diff --git a/README.md b/README.md
index 9412222614..4de6151080 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-<img src="https://github.com/syuilo/misskey/blob/b3f42e62af698a67c2250533c437569559f1fdf9/src/himasaku/resources/himasaku.png?raw=true" align="right" width="320px"/>
+<img src="https://github.com/syuilo/misskey/blob/develop/assets/ai-orig.png?raw=true" align="right" height="320px"/>
 
 [![Misskey](/assets/title.png)](https://misskey.xyz/)
 ================================================================
@@ -7,12 +7,12 @@
 [![][dependencies-badge]][dependencies-link]
 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Greenkeeper badge](https://badges.greenkeeper.io/syuilo/misskey.svg)](https://greenkeeper.io/)
 
-Sophisticated microblogging platform, evolving forever.
+**Sophisticated microblogging platform, evolving forever.**
 
 [Misskey](https://misskey.xyz) is a decentralized microblogging platform born on Earth.
 Since it exists within the Fediverse (a universe where various social media platforms are organized),
 it is mutually linked with other social media platforms.
-Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?
+Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet? [Find instance!](https://joinmisskey.github.io/)
 
 <a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
 
@@ -20,15 +20,44 @@ Why don't you take a short break from the hustle and bustle of the city, and div
 
 :sparkles: Features
 ----------------------------------------------------------------
-* Rich text contents
-* Reactions
-* User lists
-* Customizable column view (called MisskeyDeck)
-* Customizable widgets
-* Private messages
-* ActivityPub support
 
-and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz).
+<img src="/assets/about/post.png" align="left" height="200px"/>
+
+<h3 align="left">Posting</h3>
+<p align="left">
+Just post your idea, hot topics and anything you want to share. You may want to decorate your words, attach your favorite pictures, send files including movies and create a poll - those are the things you can do on Misskey!
+</p>
+
+---
+
+<img src="/assets/about/reaction.png" align="right" height="200px"/>
+
+<h3 align="right">Reactions</h3>
+<p align="right">
+Easiest way to tell your emotions. Misskey allows you to add various type of reactions to other’s post. The emotional experience on Misskey will never be on other SNSs which only able to push “likes”.
+</p>
+
+---
+
+<img src="/assets/about/ui.png" align="left" height="200px"/>
+
+<h3 align="left">Interface</h3>
+<p align="left">
+No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. You can edit layouts of your timeline, place selectable widgets you can easily move and create your unique home as this place will be your home.
+</p>
+
+---
+
+<img src="/assets/about/drive.png" align="right" width="300px"/>
+
+<h3 align="right">Misskey Drive</h3>
+<p align="right">
+Wanna post a picture you have already uploaded? Wish to organize, name and create a folder for your uploaded files? Misskey Drive is the best solution for you. Very easy to share your files online.
+</p>
+
+---
+
+and more! You can see it with your own eyes at [misskey.xyz](https://misskey.xyz) or [other instances](https://joinmisskey.github.io/).
 
 :package: Create your own instance
 ----------------------------------------------------------------
diff --git a/assets/about/drive.png b/assets/about/drive.png
new file mode 100644
index 0000000000..c35de433a8
Binary files /dev/null and b/assets/about/drive.png differ
diff --git a/assets/about/post.png b/assets/about/post.png
new file mode 100644
index 0000000000..ba291ec665
Binary files /dev/null and b/assets/about/post.png differ
diff --git a/assets/about/reaction.png b/assets/about/reaction.png
new file mode 100644
index 0000000000..e4e7e06bc0
Binary files /dev/null and b/assets/about/reaction.png differ
diff --git a/assets/about/ui.png b/assets/about/ui.png
new file mode 100644
index 0000000000..ad102a31af
Binary files /dev/null and b/assets/about/ui.png differ
diff --git a/assets/ai-orig.png b/assets/ai-orig.png
new file mode 100644
index 0000000000..b684e2c078
Binary files /dev/null and b/assets/ai-orig.png differ
diff --git a/assets/ai.png b/assets/ai.png
new file mode 100644
index 0000000000..9c6ca56632
Binary files /dev/null and b/assets/ai.png differ
diff --git a/docs/setup.ja.md b/docs/setup.ja.md
index 2758e6f231..e1ed63cab4 100644
--- a/docs/setup.ja.md
+++ b/docs/setup.ja.md
@@ -10,7 +10,7 @@ Misskeyサーバーの構築にご関心をお寄せいただきありがとう
 
 *1.* Misskeyユーザーの作成
 ----------------------------------------------------------------
-Misskeyのrootで実行しない方がよいため、代わりにユーザーを作成します。
+Misskeyはrootユーザーで実行しない方がよいため、代わりにユーザーを作成します。
 Debianの例:
 
 ```
@@ -109,6 +109,7 @@ Restart=always
 [Install]
 WantedBy=multi-user.target
 ```
+CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。
 
 3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化
 4. `systemctl start misskey` misskeyサービスの起動
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 36177d6422..ca676c4502 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてください"
     warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
@@ -68,6 +81,15 @@ common:
     confused: "こまこまのこまり"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "今どうしてる?"
     b: "何かありましたか?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "引き分け"
     my-turn: "あなたのターンです"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
   flush: "キャッシュの削除"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "あなた"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "削除"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
   retry: "リトライ"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 6a700ab140..abb338b853 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてください"
     warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
@@ -68,6 +81,15 @@ common:
     confused: "Verwirrt"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "Was machst du gerade?"
     b: "Was ist so passiert?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "引き分け"
     my-turn: "あなたのターンです"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "Die Verbindung scheint zu funktionieren. Bitte lade die Seite neu."
   flush: "Cache leeren"
   set-version: "Version angeben"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "Einen Nutzer suchen"
   you: "Du"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "Entwickler"
   feedback: "Feedback"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "Diese Anmerkung favorisieren"
   pin: "An die Profilseite pinnen"
   delete: "Löschen"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "Direkt"
   specified-desc: "Poste nur für bestimmte Benutzer"
   private: "Privat"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "Laden"
   no-broadcasts: "Keine Broadcasts"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "Serverinformationen"
   toggle: "Sicht umschalten"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "Datei auswählen"
   upload: "Dateien von deinem PC hochladen"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "Zeige Details"
   private: "Dieser Beitrag ist eine privat"
   deleted: "Dieser Beitrag wurde entfernt"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "Laden fehlgeschlagen."
   retry: "Erneut versuchen"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "Verhalten"
   fetch-on-scroll: "Aktualisieren beim scrollen"
   fetch-on-scroll-desc: "Wenn du runterscrollst empfängt die Seite automatisch zusätzliche Inhalte."
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "Automatische Pop-out Fenster"
   auto-popout-desc: "Pop-out ein offenes Fenster wenn möglich. Diese Einstellung wird im Browser gespeichert."
   advanced: "Erweiterte Einstellungen"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "Nacht Modus"
   circle-icons: "Kreisförmige Icons"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "Übergang in Fensterköpfen"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "Profil aktualisieren"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "Profil wurde aktualisiert"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 4c0122f38b..b46dacb8ef 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of the fediverse"
   about-title: "A ⭐ of the fediverse."
   about: "Thank you for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within the Fediverse (a universe where various social media platforms are organized), it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
+  intro:
+    title: "What is Misskey?"
+    about: "Misskey is a open-source <b>decentralized microblogging service</b>. Sophisticated fully customizable Ui, varieties of reaction for posts, free file storage providing integrated management system and other advancing functions are available. Also, network system called “Fediverse” enables us to communicate with users on other SNSs. Like, if you post something, then your posts will sent not only to Misskey but also mastodon. Just imagine that the planet is sending a microwave to other planet to communication."
+    features: "Features"
+    rich-contents: "Post"
+    rich-contents-desc: "Just post your idea, hot topics and anything you want to share. You may want to decorate your words, attach your favorite pictures, send files including movies and create a poll - those are the things you can do on Misskey!"
+    reaction: "Reactions"
+    reaction-desc: "Easiest way to tell your emotions. Misskey allows you to add various type of reactions to other’s post. The emotional experience on Misskey will never be on other SNSs which only able to push “likes”."
+    ui: "Interface"
+    ui-desc: "No UI fits for everyone. Therefore, Misskey has a highly customizable UI for your taste. Make your original home by editing, adjusting layouts of timeline and placing selectable widgets you can easily customize."
+    drive: "Misskey Drive"
+    drive-desc: "Wanna post a picture you have already uploaded? Wish to organize, name and create a folder for your uploaded files? Misskey Drive is the best solution for you. Very easy to share your files online."
+    outro: "Check further Misskey-unique features on your eyes! Feeling like this is not for you, try other instances as Misskey is a decentralized SNS so that you can easily find your mates. Then, GLHF!"
   adblock:
     detected: "Please disable ad blocker."
     warning: "Some features may be unavailable or cause malfunctions if ad blocking features are enabled. <strong>Misskey is not running ads</strong>."
@@ -68,6 +81,15 @@ common:
     confused: "Confused"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "Public"
+    home: "Home"
+    home-desc: "Post to the home timeline only"
+    followers: "Followers"
+    followers-desc: "Post to followers only"
+    specified: "Direct"
+    specified-desc: "Post to specified users only"
+    private: "Private"
   note-placeholders:
     a: "What are you doing?"
     b: "What's happening?"
@@ -84,9 +106,14 @@ common:
   my-token-regenerated: "Your token has been regenerated, so you will be signed out."
   i-like-sushi: "I prefer sushi rather than pudding"
   show-reversi-board-labels: "Show row and column labels in Reversi"
-  use-contrast-reversi-stones: "Make the stone color clear"
+  use-contrast-reversi-stones: "Make the stone color clear in reversi"
   verified-user: "Verified account"
   disable-animated-mfm: "Disable animated texts in a post"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "Do not omit the hostname from the username"
+  this-setting-is-this-device-only: "Only for this device"
+  do-not-use-in-production: 'As this is for development, do not use this in production.'
   reversi:
     drawn: "Draw"
     my-turn: "Your turn"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "Looks like we have a connection. Please reload the page."
   flush: "Clean cache"
   set-version: "Specify version"
+common/views/components/cw-button.vue:
+  hide: "Hide"
+  show: "See more"
 common/views/components/messaging.vue:
   search-user: "Find a user"
   you: "You"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "Developers"
   feedback: "Feedback"
 common/views/components/note-menu.vue:
+  detail: "Details"
+  copy-link: "Copy link"
   favorite: "Favorite this note"
   pin: "Pin to your profile"
   delete: "Delete"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "Direct"
   specified-desc: "Post to specified users only"
   private: "Private"
+common/views/components/trends.vue:
+  count: "{} users mentioned"
+  empty: "No popular hashtag trends"
 common/views/widgets/broadcast.vue:
   fetching: "Fetching"
   no-broadcasts: "No announcements"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "Toggle views"
 common/views/widgets/hashtags.vue:
   title: "Hashtags"
-  count: "{} users mentioned"
-  empty: "No popular hashtag trends"
 common/views/widgets/server.vue:
   title: "Server info"
   toggle: "Toggle views"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "Posts"
   users: "Users"
   drive: "Drive"
+  network: "Network"
   charts:
     notes: "The number of posts: increase/decrease (Combined)"
     local-notes: "The number of posts: increase/decrease (Local)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "Capacity used as the storage: cumulative total"
     drive-files: "The number of files on the storage: increase/decrease"
     drive-files-total: "The number of files on the storage: cumulative total"
+    network-requests: "Requests"
+    network-time: "Response time"
+    network-usage: "Traffic"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "Choose files"
   upload: "Upload files from your device"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "Show details"
   private: "Post is private"
   deleted: "Post has been deleted"
-  hide: "Hide"
-  see-more: "See more"
 desktop/views/components/notes.vue:
   error: "Loading failed."
   retry: "Retry"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "Behavior"
   fetch-on-scroll: "Endless loading on scroll"
   fetch-on-scroll-desc: "When you scroll down the page, it automatically fetches additional content."
+  note-visibility: "Post visibility"
+  default-note-visibility: "Default visibility"
+  remember-note-visibility: "Remember post visibility"
   auto-popout: "Auto pop-out window"
   auto-popout-desc: "If it's possible, pop-out display will be used instead of opening a new window. This setting is stored in your browser."
   advanced: "Advanced settings"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "Remove background"
   dark-mode: "Dark Mode"
   circle-icons: "Use circle icons"
+  contrasted-acct: "Add contrast to username"
   gradient-window-header: "Use gradients on window headers"
   post-form-on-timeline: "Display post form at the top of the timeline"
   suggest-recent-hashtags: "Show recent popular hashtags on the post form"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "Birthday"
   save: "Update profile"
   locked-account: "Protect your account"
-  is-locked: "Make your posts private"
+  is-locked: "Follow request needs approval"
   other: "Other"
   is-bot: "This account is a Bot"
   is-cat: "This account is a Cat"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "Logging in..."
   signup-button: "Sign up"
   timeline: "Timeline"
+  announcements: "Announcements"
+  photos: "Recent uploaded"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "Information"
 desktop/views/pages/drive.vue:
   title: "Misskey storage"
 desktop/views/pages/favorites.vue:
@@ -897,7 +939,7 @@ desktop/views/pages/user/user.friends.vue:
   no-users: "No frequent mentions"
 desktop/views/pages/user/user.vue:
   is-suspended: "This account has been suspended."
-  is-remote: "The user is a remote user. The profile that you see here may not complete."
+  is-remote: "This profile belongs to a remote user. The profile that you see here may not be complete. "
   view-remote: "See their complete profile"
 desktop/views/pages/user/user.home.vue:
   last-used-at: "Last active:"
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "Close"
 mobile/views/components/note.vue:
   reposted-by: "Reposted by {}"
-  more: "See more"
-  less: "Hide"
   private: "This post is private"
   deleted: "This post has been deleted"
   location: "Location"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "Avatar"
   banner: "Banner"
   is-cat: "This account is a Cat"
+  is-locked: "Follow request needs approval"
+  advanced: "Advanced"
+  privacy: "Privacy"
   save: "Update profile"
   saved: "Profile updated"
   uploading: "Uploading"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "Dark Mode"
   i-am-under-limited-internet: "I'm in limited bandwidth"
   circle-icons: "Use circle icons"
+  contrasted-acct: "Add contrast to username"
   timeline: "Timeline"
   show-reply-target: "Show reply target"
   show-my-renotes: "Show my reposts"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "Post design"
   post-style-standard: "Standard"
   post-style-smart: "Smart"
+  notification-position: "Notification style"
+  notification-position-bottom: "Bottom"
+  notification-position-top: "Top"
   behavior: "Behavior"
   fetch-on-scroll: "Endless loading on scroll"
+  note-visibility: "Post visibility"
+  default-note-visibility: "Default visibility"
+  remember-note-visibility: "Remember post visibility"
   disable-via-mobile: "Don't mark the post as 'from mobile'"
   load-raw-images: "Show attached images in original quality"
   load-remote-media: "Show media from a remote server"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "Settings"
   signout: "Sign out"
   sound: "Sounds"
-  enableSounds: "Enable sounds"
+  enable-sounds: "Enable sounds"
 mobile/views/pages/user.vue:
   follows-you: "Follows you"
   following: "Following"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 05c5510133..b26cf9c811 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -6,12 +6,25 @@ common:
   misskey: "Una ⭐️ del fediverso"
   about-title: "Una ⭐️ del fediverso"
   about: "Gracias por encontrae Misskey. Misskey es una <b>plataforma descentralizada de microblogging</b> nacida en la Tierra. Gracias a existir dentro del Fediverso (un universo donde se organizan varias plataformas sociales) se encuentra enlazada mutuamente con otras plataformas sociales. ¿Por què no te tomas un respiro del caos de la ciudad y te sumerges es una nueva manera de entender Internet?"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "Por favor, desactive el bloqueador de publicidad."
     warning: "<strong>Misskey no tiene anuncios publicitarios.</strong> Sin embargo, algunas características podrían no estar disponibles si el bloqueador de publicidad está habilitado."
   application-authorization: "Autorizaciones de la aplicación."
   close: "Cerrar"
-  do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
+  do-not-copy-paste: "Por favor no copies código aquí. Tu cuenta puede resultar comprometida."
   got-it: "¡Listo!"
   customization-tips:
     title: "Consejos de personalización"
@@ -58,7 +71,7 @@ common:
     friday: "Viernes"
     saturday: "Sábado"
   reactions:
-    like: "いいね"
+    like: "Me gusta"
     love: "amor"
     laugh: "risa"
     hmm: "hmm"
@@ -68,6 +81,15 @@ common:
     confused: "confundido"
     rip: "RIP"
     pudding: "Chafado"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "¿Qué haces?"
     b: "¿Qué está pasando?"
@@ -84,9 +106,14 @@ common:
   my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado."
   i-like-sushi: "Prefiero sushi a pudín"
   show-reversi-board-labels: "Mostrar etiquetas de filas y columnas en Reversi"
-  use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
-  verified-user: "公式アカウント"
+  use-contrast-reversi-stones: "Hacer el color de la piedra claro en Reversi"
+  verified-user: "Cuenta verificada"
   disable-animated-mfm: "Desactivar texto animado en una publicación"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'Esto está en desarrollo, no usarlo para producción.'
   reversi:
     drawn: "Empatado"
     my-turn: "Mi turno"
@@ -170,9 +197,9 @@ common/views/components/games/reversi/reversi.vue:
 common/views/components/games/reversi/reversi.game.vue:
   surrender: "Rendirse"
   surrendered: "Por rendirse"
-  is-llotheo: "石の少ない方が勝ち(ロセオ)"
-  looped-map: "ループマップ"
-  can-put-everywhere: "どこでも置けるモード"
+  is-llotheo: "El último gana (Llotheo)"
+  looped-map: "Mapa en bucle"
+  can-put-everywhere: "Puedes colocar donde quieras"
 common/views/components/games/reversi/reversi.index.vue:
   title: "Misskey Reversi"
   sub-title: "¡Juega Reversi con tus amigos!"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "Parece que la conexión ha sido posible. Por favor refresca la página."
   flush: "Limpiar la memoria caché"
   set-version: "Escoge la versión"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "Encuentra un usuario"
   you: "Tu"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "Desarrolladores"
   feedback: "Opiniones"
 common/views/components/note-menu.vue:
+  detail: "Detalles"
+  copy-link: "Copiar enlace"
   favorite: "Me gusta esta nota"
   pin: "Fijar en el perfil"
   delete: "Borrar"
@@ -288,10 +320,10 @@ common/views/components/signin.vue:
   signin: "Entra"
   or: "O"
   signin-with-twitter: "Ingresar con Twitter"
-  login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
+  login-failed: "Autenticación fallida. Asegúrate de haber usado el nombre de usuario y contraseña correctos."
 common/views/components/signup.vue:
-  invitation-code: "招待コード"
-  invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
+  invitation-code: "Código de invitación"
+  invitation-info: "Si no tienes un código de invitación, por favor contacta un <a href=\"{}\">administrador</a>."
   username: "Usuario"
   checking: "Comprobando..."
   available: "Disponible"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "Directo"
   specified-desc: "Publica solo para los seguidores que quieras"
   private: "Privada"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "Recuperando"
   no-broadcasts: "Sin emisión"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "Alternar vistas"
 common/views/widgets/hashtags.vue:
   title: "Etiquetas"
-  count: "{} usuarios mencionados"
-  empty: "Ninguna tendencia popular ahora"
 common/views/widgets/server.vue:
   title: "Información del servidor"
   toggle: "Alternar vistas"
@@ -411,7 +444,7 @@ desktop:
   uploading-avatar: "Cargando un nuevo avatar"
   avatar-updated: "Avatar actualizado"
   choose-avatar: "Escoge una imagen de avatar"
-  invalid-filetype: "この形式のファイルはサポートされていません"
+  invalid-filetype: "Este tipo de archivo no es compatible aquí"
 desktop/views/components/activity.chart.vue:
   total: "Negro ... Total"
   notes: "Azul ... Notas"
@@ -426,23 +459,27 @@ desktop/views/components/calendar.vue:
   next: "Próximo mes"
   go: "Click para navegar"
 desktop/views/components/charts.vue:
-  title: "チャート"
-  per-day: "1日ごと"
-  per-hour: "1時間ごと"
-  notes: "投稿"
-  users: "ユーザー"
-  drive: "ドライブ"
+  title: "Gráficos"
+  per-day: "por día"
+  per-hour: "por hora"
+  notes: "Publicaciones"
+  users: "Usuarios"
+  drive: "Unidad"
+  network: "ネットワーク"
   charts:
-    notes: "投稿の増減 (統合)"
-    local-notes: "投稿の増減 (ローカル)"
-    remote-notes: "投稿の増減 (リモート)"
-    notes-total: "投稿の累計"
-    users: "ユーザーの増減"
-    users-total: "ユーザーの累計"
-    drive: "ドライブ使用量の増減"
-    drive-total: "ドライブ使用量の累計"
-    drive-files: "ドライブのファイル数の増減"
-    drive-files-total: "ドライブのファイル数の累計"
+    notes: "Número de publicaciones: aumentar/disminuir (Combinado)"
+    local-notes: "Número de publicaciones: aumentar/disminuir (Local)"
+    remote-notes: "Número de publicaciones: aumentar/disminuir (Remoto)"
+    notes-total: "Número de publicaciones: Acumulativo total"
+    users: "Número de usuarios: aumentar/disminuir"
+    users-total: "Número de usuarios: Acumulativo total"
+    drive: "Capacidad de almacenamiento usada: aumentar/disminuir"
+    drive-total: "Capacidad de almacenamiento usada: Acumulativa total"
+    drive-files: "Número de archivos almacenados: aumentar/disminuir"
+    drive-files-total: "Número de archivos almacenados: Acumulativo total"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "Escoger archivos"
   upload: "Cargar archivos de tu dispositivo"
@@ -463,7 +500,7 @@ desktop/views/components/drive-window.vue:
 desktop/views/components/drive.file.vue:
   avatar: "Avatar"
   banner: "Banner"
-  nsfw: "閲覧注意"
+  nsfw: "Ver más"
   contextmenu:
     rename: "Renombrar"
     mark-as-sensitive: "Marcar como 'sensible'"
@@ -515,31 +552,31 @@ desktop/views/components/media-image.vue:
   sensitive: "El contenido es NSFW (no seguro para ver en el trabajo, 'not safe for work')"
   click-to-show: "Click para mostrar"
 desktop/views/components/media-video.vue:
-  sensitive: "閲覧注意"
-  click-to-show: "クリックして表示"
+  sensitive: "Este contenido no es apropiado para ver en el trabajo"
+  click-to-show: "Click para mostrar"
 desktop/views/components/follow-button.vue:
   following: "Siguiendo"
   follow: "Sigue"
   request-pending: "Pendiente de aprobación"
-  follow-request: "フォロー申請"
+  follow-request: "Solicitud de seguir"
 desktop/views/components/followers-window.vue:
-  followers: "{} のフォロワー"
+  followers: "{} seguidores"
 desktop/views/components/followers.vue:
-  empty: "フォロワーはいないようです。"
+  empty: "Parece que no tienes seguidores aún."
 desktop/views/components/following-window.vue:
-  following: "{} のフォロー"
+  following: "Siguiendo {}"
 desktop/views/components/following.vue:
-  empty: "フォロー中のユーザーはいないようです。"
+  empty: "Parece que aún no sigues a nadie."
 desktop/views/components/friends-maker.vue:
-  title: "気になるユーザーをフォロー:"
-  empty: "おすすめのユーザーは見つかりませんでした。"
-  fetching: "読み込んでいます"
-  refresh: "もっと見る"
-  close: "閉じる"
+  title: "Usuarios recomendados:"
+  empty: "No se pudieron encontrar usuarios para recomendar"
+  fetching: "Cargando"
+  refresh: "Más"
+  close: "Cerrar"
 desktop/views/components/game-window.vue:
-  game: "リバーシ"
+  game: "Reversi"
 desktop/views/components/home.vue:
-  done: "完了"
+  done: "Listo"
   add-widget: "Agregar accesorio:"
   add: "Agregar"
 desktop/views/input-dialog.vue:
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "Mostrar detalles"
   private: "Esta publicación es privada"
   deleted: "Esta publicación ha sido borrada"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "Error al cargar."
   retry: "Reintentar"
@@ -602,7 +637,7 @@ desktop/views/components/post-form.vue:
   geolocation-alert: "Tu dispositivo no tiene soporte de geolocalización."
   error: "Error"
   enter-username: "Por favor escribe un nombre de usuario..."
-  annotations: "内容への注釈 (オプション)"
+  annotations: "Anotaciones a la publicación (opcional)"
 desktop/views/components/post-form-window.vue:
   note: "Nota nueva"
   reply: "Responder"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "Acciones"
   fetch-on-scroll: "Desplazamiento infinito"
   fetch-on-scroll-desc: "Cuando te deslizas al final de la página nuevo contenido se carga automáticamente."
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "Ventana emergente automática"
   auto-popout-desc: "Muestra una ventana emergente si es posible. Esta configuración depende del navegador."
   advanced: "Configuración avanzada"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "Suprimir fondo"
   dark-mode: "Modo Nocturno"
   circle-icons: "Usar iconos circulares"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "Usar degradados en las cabeceras de las páginas"
   post-form-on-timeline: "Mostrar el formulario de las entradas encima de la línea de tiempo"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "Fecha de nacimiento"
   save: "Perfil actualizado"
   locked-account: "Protege tu cuenta"
-  is-locked: "Crear una nota privada"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -766,40 +805,40 @@ desktop/views/components/timeline.vue:
   global: "グローバル"
   list: "リスト"
 desktop/views/components/ui.header.vue:
-  welcome-back: "おかえりなさい、"
-  adjective: "さん"
+  welcome-back: "Bienvenido/a de vuelta,"
+  adjective: "-san"
 desktop/views/components/ui.header.account.vue:
-  profile: "プロフィール"
-  drive: "ドライブ"
-  favorites: "お気に入り"
-  lists: "リスト"
-  follow-requests: "フォロー申請"
-  customize: "ホームのカスタマイズ"
-  admin: "管理"
-  settings: "設定"
-  signout: "サインアウト"
-  dark: "闇に飲まれる"
+  profile: "Tu perfil"
+  drive: "Unidad"
+  favorites: "Favoritos"
+  lists: "Listas"
+  follow-requests: "Solicitudes de seguimiento"
+  customize: "Personalizar la página de inicio"
+  admin: "Admin"
+  settings: "Configuraciones"
+  signout: "Desconectarse"
+  dark: "Sumergirse en la oscuridad"
 desktop/views/components/ui.header.nav.vue:
-  home: "ホーム"
-  deck: "デッキ"
-  messaging: "メッセージ"
-  game: "ゲーム"
+  home: "Inicio"
+  deck: "Cubierta"
+  messaging: "Mensajes"
+  game: "Juegos"
 desktop/views/components/ui.header.notifications.vue:
-  title: "通知"
+  title: "Notificaciones"
 desktop/views/components/ui.header.post.vue:
-  post: "新規投稿"
+  post: "Crear una publicación"
 desktop/views/components/ui.header.search.vue:
-  placeholder: "検索"
+  placeholder: "Buscar"
 desktop/views/components/received-follow-requests-window.vue:
-  title: "フォロー申請"
-  accept: "承認"
-  reject: "拒否"
+  title: "Solicitudes de seguidores"
+  accept: "Aceptar"
+  reject: "Rechazar"
 desktop/views/components/user-lists-window.vue:
-  title: "リスト"
-  create-list: "リストを作成"
-  list-name: "リスト名"
+  title: "Listas de usuario"
+  create-list: "Crear lista"
+  list-name: "Nombre de lista"
 desktop/views/components/user-preview.vue:
-  notes: "投稿"
+  notes: "Publicaciones"
   following: "フォロー"
   followers: "フォロワー"
 desktop/views/components/users-list.vue:
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index f936c2579b..0d899e3dda 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -6,6 +6,19 @@ common:
   misskey: "Une ⭐ du fédiverse"
   about-title: "Une ⭐ du fédivers."
   about: "Merci d'avoir découvert Misskey. Misskey est une <b>plateforme de microblogage distribuée</b> née sur Terre. Parce qu'il fait partie du Fédivers (un univers composé de diverses plateformes de réseaux sociaux organisées), il est mutuellement connecté avec d'autres plateformes de réseaux sociaux. Désirez-vous prendre une pause, pendant un instant, loin de l'agitation de la ville et plonger dans un nouvel Internet ?"
+  intro:
+    title: "C’est quoi Misskey ?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "Fonctionnalités"
+    rich-contents: "Notes"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "Réactions"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "Interface utilisateur"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "Drive"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "Veuillez désactiver votre bloqueur de publicités"
     warning: "<strong>Misskey n'utilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
@@ -68,6 +81,15 @@ common:
     confused: "Confus"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "Public"
+    home: "Accueil"
+    home-desc: "Publier sur le fil local uniquement"
+    followers: "Abonnés·es"
+    followers-desc: "Publier à vos abonnés·es uniquement"
+    specified: "Direct"
+    specified-desc: "Publier aux utilisateurs·trices mentionnés·es"
+    private: "Privé"
   note-placeholders:
     a: "Que faites-vous maintenant ?"
     b: "Quoi de neuf ?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "Compte vérifié"
   disable-animated-mfm: "Désactiver les textes animés dans les publications"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "Partie nulle"
     my-turn: "C’est votre tour"
@@ -172,7 +199,7 @@ common/views/components/games/reversi/reversi.game.vue:
   surrendered: "Par abandon"
   is-llotheo: "石の少ない方が勝ち(ロセオ)"
   looped-map: "Carte en boucle"
-  can-put-everywhere: "どこでも置けるモード"
+  can-put-everywhere: "Peut poser partout"
 common/views/components/games/reversi/reversi.index.vue:
   title: "Misskey Reversi"
   sub-title: "Jouer à Reversi avec vos ami·e·s !"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "Succès de la connexion au serveur de Misskey. Veuillez recharger la page."
   flush: "Vider le cache"
   set-version: "Choisissez une version"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "Trouver un·e utilisateur·trice"
   you: "Vous"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "Développeur·se·s"
   feedback: "Remarques"
 common/views/components/note-menu.vue:
+  detail: "Détails"
+  copy-link: "Copier le lien"
   favorite: "Mettre cette note en favoris"
   pin: "Épingler sur votre profil"
   delete: "Supprimer"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "Direct"
   specified-desc: "Publier aux utilisateur·rice·s mentionné·e·s"
   private: "Privé"
+common/views/components/trends.vue:
+  count: "{} utilisateurs·trices mentionnés·es"
+  empty: "Aucune tendance"
 common/views/widgets/broadcast.vue:
   fetching: "Récupération"
   no-broadcasts: "Aucune annonce"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "Basculer entre les vues"
 common/views/widgets/hashtags.vue:
   title: "Étiquettes"
-  count: "{} utilisateur·rice·s mentionné·e·s"
-  empty: "Aucune tendance"
 common/views/widgets/server.vue:
   title: "Informations sur le serveur"
   toggle: "Afficher les vues"
@@ -391,7 +424,7 @@ common/views/widgets/tips.vue:
   tips-line19: "いくつかのウィンドウはブラウザの外に切り離すことができます"
   tips-line20: "カレンダーウィジェットのパーセンテージは、経過の割合を示しています"
   tips-line21: "Vous pouvez aussi utiliser l'API pour développer des Bots."
-  tips-line23: "まゆかわいいよまゆ"
+  tips-line23: "Mayu est mignone avec ses sourcils."
   tips-line24: "Misskey a vu le jour en 2014"
   tips-line25: "対応ブラウザではMisskeyを開いていなくても通知を受け取れます"
 common/views/pages/follow.vue:
@@ -432,17 +465,21 @@ desktop/views/components/charts.vue:
   notes: "Publications"
   users: "Utilisateurs"
   drive: "Drive"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
     remote-notes: "投稿の増減 (リモート)"
     notes-total: "投稿の累計"
-    users: "ユーザーの増減"
-    users-total: "ユーザーの累計"
+    users: "Nombre d’utilisateurs·trices : augmentation/diminution"
+    users-total: "Nombre total d’utilisateurs·trices : total cumulé"
     drive: "ドライブ使用量の増減"
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "Sélection de fichiers"
   upload: "Téléverser des fichiers à partir de votre ordinateur"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "Afficher les détails"
   private: "cette publication est privée"
   deleted: "cette publication a été supprimée"
-  hide: "Masquer"
-  see-more: "Voir plus"
 desktop/views/components/notes.vue:
   error: "Échec du chargement."
   retry: "Réessayer"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "Comportement"
   fetch-on-scroll: "Chargement lors du défilement"
   fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
+  note-visibility: "Visibilité de la publication"
+  default-note-visibility: "Visibilité par défaut"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "Fenêtre contextuelle automatique"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "Paramètres avancés"
@@ -647,8 +685,9 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "Supprimer le fond d'écran"
   dark-mode: "Mode nuit"
   circle-icons: "Utiliser des icônes circulaires"
+  contrasted-acct: "Nom d’utilisateur contrasté"
   gradient-window-header: "Utiliser les dégradés sur la barre de titre de la fenêtre"
-  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
+  post-form-on-timeline: "Afficher le formulaire en haut du fil"
   suggest-recent-hashtags: "Afficher les hashtags populaires dans le champs de saisie"
   show-clock-on-header: "Afficher l'horloge à droite sur le coté supérieur"
   show-reply-target: "Afficher les réponses"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "Date de naissance"
   save: "Mettre à jour le profil"
   locked-account: "Protéger votre compte"
-  is-locked: "Rendre la note privée"
+  is-locked: "フォローを承認制にする"
   other: "Autre"
   is-bot: "Ce compte est un Bot"
   is-cat: "Ce compte est un Chat"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "Se connecter"
   signup-button: "S'inscrire"
   timeline: "Fil d'actualité"
+  announcements: "Notices"
+  photos: "Images récentes"
   powered-by-misskey: "Propulsé par <b>Misskey</b>."
+  info: "Informations"
 desktop/views/pages/drive.vue:
   title: "Lecteur de Misskey"
 desktop/views/pages/favorites.vue:
@@ -971,14 +1013,14 @@ mobile/views/components/drive-file-chooser.vue:
 mobile/views/components/drive-folder-chooser.vue:
   select-folder: "Choisissez un dossier"
 mobile/views/components/drive.file.vue:
-  nsfw: "閲覧注意"
+  nsfw: "CW"
 mobile/views/components/drive.file-detail.vue:
   download: "Télécharger"
   rename: "Renommer"
   move: "Déplacer"
   hash: "Hash (md5)"
   exif: "EXIF"
-  nsfw: "閲覧注意"
+  nsfw: "CW"
 mobile/views/components/media-image.vue:
   sensitive: "Le contenu est NSFW"
   click-to-show: "Cliquer pour afficher"
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "Fermer"
 mobile/views/components/note.vue:
   reposted-by: "Renoté par {}"
-  more: "Voir plus"
-  less: "Masquer"
   private: "cette publication est privée"
   deleted: "cette publication a été supprimée"
   location: "Géolocalisation"
@@ -1045,7 +1085,7 @@ mobile/views/components/timeline.vue:
   load-more: "Afficher plus"
 mobile/views/components/ui.header.vue:
   welcome-back: "Content de vous revoir ! "
-  adjective: "さん"
+  adjective: "M."
 mobile/views/components/ui.nav.vue:
   timeline: "Fil d'actualité"
   notifications: "Notifications"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "Avatar"
   banner: "Bannière"
   is-cat: "Ce compte est un Bot"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "Mettre à jour le profil"
   saved: "Profil mis à jour avec succès"
   uploading: "En cours d'envoi"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "Mode nuit"
   i-am-under-limited-internet: "J'ai un accès Internet limité"
   circle-icons: "Utiliser des icônes circulaires"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "Fil d'actualité"
   show-reply-target: "Afficher les réponses"
   show-my-renotes: "Afficher mes republications"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "Style de la publication"
   post-style-standard: "Standard"
   post-style-smart: "Intelligent"
+  notification-position: "Style de notification"
+  notification-position-bottom: "en bas"
+  notification-position-top: "en haut"
   behavior: "Comportement"
   fetch-on-scroll: "Chargement lors du défilement"
+  note-visibility: "Visibilité de la publication"
+  default-note-visibility: "Visibilité par défaut"
+  remember-note-visibility: "Se souvenir du mode de visibilité de la publication"
   disable-via-mobile: "Ne pas mentionner que ma publication provient d'un 'périphérique mobile'"
   load-raw-images: "Afficher les photos jointes en haute qualité"
   load-remote-media: "Afficher les médias sur le serveur distant"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "Réglages"
   signout: "Déconnexion"
   sound: "Sons"
-  enableSounds: "Activer le son"
+  enable-sounds: "Activer les sons"
 mobile/views/pages/user.vue:
   follows-you: "vous suit"
   following: "Abonnements"
@@ -1223,7 +1273,7 @@ docs:
       res: "Réponse"
       require-credential: "Ce point de communication nécessite une authentification."
       require-permission: "Ce point de communication nécessite la permission {permission}."
-      has-limit: "レートリミットがあります。"
+      has-limit: "Il y’a un taux limite."
       duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
       min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
       show-src: "Vous pouvez voir le code source ce point de communication."
diff --git a/locales/index.js b/locales/index.js
index 1f28d3ff03..6780251e10 100644
--- a/locales/index.js
+++ b/locales/index.js
@@ -5,7 +5,7 @@
 const fs = require('fs');
 const yaml = require('js-yaml');
 
-const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES'];
+const langs = ['de-DE', 'en-US', 'fr-FR', 'ja-JP', 'ja-KS', 'pl-PL', 'es-ES', 'nl-NL'];
 
 const loadLocale = lang => yaml.safeLoad(fs.readFileSync(`${__dirname}/${lang}.yml`, 'utf-8'));
 const locales = langs.map(lang => ({ [lang]: loadLocale(lang) }));
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 36177d6422..ca676c4502 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてください"
     warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
@@ -68,6 +81,15 @@ common:
     confused: "こまこまのこまり"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "今どうしてる?"
     b: "何かありましたか?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "引き分け"
     my-turn: "あなたのターンです"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
   flush: "キャッシュの削除"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "あなた"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "削除"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
   retry: "リトライ"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index de5505e7db..3afb809757 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてください"
     warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
@@ -73,6 +86,16 @@ common:
     rip: "RIP"
     pudding: "Pudding"
 
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
+
   note-placeholders:
     a: "今どうしてる?"
     b: "何かありましたか?"
@@ -93,6 +116,10 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
 
   do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
 
@@ -254,6 +281,10 @@ common/views/components/media-banner.vue:
   sensitive: "閲覧注意"
   click-to-show: "クリックして表示"
 
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
+
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "あなた"
@@ -491,6 +522,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -502,6 +534,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
@@ -647,8 +682,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
@@ -728,6 +761,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -740,6 +776,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -855,7 +892,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -994,7 +1031,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
@@ -1166,8 +1206,6 @@ mobile/views/components/friends-maker.vue:
 
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1327,6 +1365,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1351,6 +1392,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1359,8 +1401,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1380,7 +1428,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index ddb2f3b788..a2ee914fdb 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "ようMisskeyを見つけてくれて、おおきにやで。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>やねん。Fediverse(ぎょうさんのSNSで構成されとる宇宙)っちゅうもんの中におるから、お隣さんのSNSとも仲良うさせてもろてんねん。ちょいとやかましい心斎橋から離れて、新しいインターネットにダイブしてみぃひん?"
+  intro:
+    title: "Misskeyってなんやねん"
+    about: "Misskeyってのはな、オープンソースの<b>分散型マイクロブログSNS</b>のことや。ごっついええ感じにできるUIやったり、投稿へのリアクションやったり、ファイルをまとめとけるドライブやったり、いろんな機能が目白押しや。Fediverseに対応しとるから、よそのSNSともノリツッコミできるんやで。タイガースが東京ドームに野球しに行くようなもんや。"
+    features: "ええとこ"
+    rich-contents: "投稿"
+    rich-contents-desc: "思っとること、タイガースの実況、他に言いたいことがあればなんでも言ってええで。いろんな構文あるから、好きにつこうてくれや。画像や動画、アンケートも添付できるで。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてや"
     warning: "<strong>Misskeyは広告を掲載してへん</strong>けど、広告をブロックしはる機能がおると一部の機能が利用できんくなったり、不具合が発生するかも分からん。知らんけど。"
@@ -68,6 +81,15 @@ common:
     confused: "こまこまのこまりやわぁ"
     rip: "RIP"
     pudding: "アメちゃんちゃうんちゃう?"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "今なにしてん?"
     b: "何かあったんか?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストをつけんで!"
   verified-user: "アメちゃん付きアカウント"
   disable-animated-mfm: "投稿内のちょろちょろ動いてんのを止める"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "おあいこ"
     my-turn: "あんさんのターンや"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようやわ。ページを再度読み込みしてな。"
   flush: "キャッシュの削除"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "あんさん"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "もっと"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "ほかす"
@@ -286,7 +318,7 @@ common/views/components/signin.vue:
   token: "トークン"
   signing-in: "サインイン中や..."
   signin: "サインイン"
-  or: "または"
+  or: "それか"
   signin-with-twitter: "Twitterでサインイン"
   login-failed: "なんかログインできんかったわ。ユーザー名とパスワードとかを確認してや。"
 common/views/components/signup.vue:
@@ -296,12 +328,12 @@ common/views/components/signup.vue:
   checking: "確認中や…"
   available: "使えるで"
   unavailable: "もう使われとるで"
-  error: "通信エラー"
+  error: "通信あかんわ"
   invalid-format: "a~z、A~Z、0~9、_が使えるで"
-  too-short: "1文字以上でお願いします!"
+  too-short: "1文字以上やで!"
   too-long: "20文字以内でお願いします"
   password: "パスワード"
-  password-placeholder: "8文字以上を推奨します"
+  password-placeholder: "8文字以上にしときや"
   weak-password: "へぼいパスワード"
   normal-password: "ぼちぼちなパスワード"
   strong-password: "良さげなパスワード"
@@ -316,18 +348,18 @@ common/views/components/special-message.vue:
   new-year: "Happy New Year!"
   christmas: "Merry Christmas!"
 common/views/components/stream-indicator.vue:
-  connecting: "接続中"
-  reconnecting: "再接続中"
-  connected: "接続完了"
+  connecting: "つないどるで"
+  reconnecting: "つなぎ直すで"
+  connected: "つないだわ"
 common/views/components/twitter-setting.vue:
   description: "あんさんがつことるTwitterアカウントをMisskeyアカウントに接続しとくと、あんさんのプロフィールにTwitterアカウント情報が表示されるようになったり、Twitterをつこた便利なサインインが使えるようになったりすんで。"
   connected-to: "次のTwitterアカウントに接続されとるで"
   detail: "詳細..."
-  reconnect: "再接続する"
+  reconnect: "つなぎ直す"
   connect: "Twitterと接続する"
-  disconnect: "切断する"
+  disconnect: "さいならする"
 common/views/components/uploader.vue:
-  waiting: "待機中"
+  waiting: "待っとる"
 common/views/components/visibility-chooser.vue:
   public: "公開"
   home: "ホーム"
@@ -337,8 +369,11 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
-  fetching: "確認中"
+  fetching: "見てみるわ…"
   no-broadcasts: "お知らせはあらへんで"
   have-a-nice-day: "良い一日を!"
   next: "次"
@@ -360,19 +395,17 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "流行は自分で作るんや"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
 common/views/widgets/memo.vue:
   title: "付箋"
-  memo: "ここに書いて!"
+  memo: "書くんや!"
   save: "保存"
 common/views/widgets/slideshow.vue:
   folder-customize-mode: "フォルダを指定するんやったら、一旦カスタマイズモードを終了してや"
-  folder: "クリックしてフォルダを指定してください"
-  no-image: "このフォルダには画像がありません"
+  folder: "クリックしてフォルダ決めてや"
+  no-image: "このフォルダには画像無いわ"
 common/views/widgets/tips.vue:
   tips-line1: "<kbd>t</kbd>でタイムラインにフォーカスできんで"
   tips-line2: "<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開くで"
@@ -405,13 +438,13 @@ desktop:
   banner: "バナー"
   uploading-banner: "新しいバナーをアップロードしとるで"
   banner-updated: "バナーを更新したで"
-  choose-banner: "バナーにする画像を選択"
-  avatar-crop-title: "アバターとして表示する部分を選択"
+  choose-banner: "バナーにする画像選んでや"
+  avatar-crop-title: "どこアバターとして出しとく?"
   avatar: "アバター"
   uploading-avatar: "新しいアバターをアップロードしています"
   avatar-updated: "アバターを更新しました"
   choose-avatar: "アバターにする画像を選択"
-  invalid-filetype: "この形式のファイルはサポートされていません"
+  invalid-filetype: "この形式のファイル無理やねん"
 desktop/views/components/activity.chart.vue:
   total: "Black ... Total"
   notes: "Blue ... Notes"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -463,29 +500,29 @@ desktop/views/components/drive-window.vue:
 desktop/views/components/drive.file.vue:
   avatar: "アイコン"
   banner: "バナー"
-  nsfw: "閲覧注意"
+  nsfw: "見たらあかんで"
   contextmenu:
     rename: "名前を変えるで"
-    mark-as-sensitive: "閲覧注意に設定"
-    unmark-as-sensitive: "閲覧注意を解除"
+    mark-as-sensitive: "見たらあかん感じにしとく"
+    unmark-as-sensitive: "やっぱ見せたるわ"
     copy-url: "URLをコピー"
     download: "ダウンロード"
-    else-files: "その他..."
-    set-as-avatar: "アイコンに設定"
-    set-as-banner: "バナーに設定"
+    else-files: "もっとあるで…"
+    set-as-avatar: "アイコンにする"
+    set-as-banner: "バナーにする"
     open-in-app: "アプリで開く"
-    add-app: "アプリを追加"
-    rename-file: "ファイル名の変更"
+    add-app: "アプリ増やす"
+    rename-file: "ファイル名をいらう(変える)"
     input-new-file-name: "新しいファイル名を入力してや"
     copied: "コピー完了や"
-    copied-url-to-clipboard: "URLをクリップボードにコピーしました"
+    copied-url-to-clipboard: "URLをクリップボードに写したわ"
 desktop/views/components/drive.folder.vue:
-  unable-to-process: "操作を完了できません"
-  circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
+  unable-to-process: "あかん、無理やわ"
+  circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーや。"
   unhandled-error: "ようわからん"
   contextmenu:
-    move-to-this-folder: "このフォルダへ移動"
-    show-in-new-window: "新しいウィンドウで表示"
+    move-to-this-folder: "ここに持ってくるわ"
+    show-in-new-window: "新しいウィンドウで出す"
     rename: "名前を変えるで"
     rename-folder: "フォルダ名を変えるで"
     input-new-folder-name: "新しいフォルダ名を入力してや"
@@ -493,24 +530,24 @@ desktop/views/components/drive.nav-folder.vue:
   drive: "ドライブ"
 desktop/views/components/drive.vue:
   search: "検索"
-  load-more: "もっと読み込む"
+  load-more: "もっとあらへんのか!"
   empty-draghover: "ドロップですか?いいですよ、ボクはカワイイですからね"
   empty-drive: "ドライブには何もあらへんで。"
   empty-drive-description: "右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。"
   empty-folder: "このフォルダーは空です"
-  unable-to-process: "操作を完了できません"
+  unable-to-process: "あかん、無理やわ"
   circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
-  unhandled-error: "不明なエラー"
+  unhandled-error: "ようわからん"
   url-upload: "URLアップロード"
-  url-of-file: "アップロードしたいファイルのURL"
-  url-upload-requested: "アップロードをリクエストしました"
+  url-of-file: "このURLのファイルをアップロードしたいねん"
+  url-upload-requested: "アップロードしたい言うといたで"
   may-take-time: "アップロードが完了するまで時間がかかる場合があります。"
   create-folder: "フォルダー作成"
   folder-name: "フォルダー名"
   contextmenu:
-    create-folder: "フォルダーを作成"
-    upload: "ファイルをアップロード"
-    url-upload: "URLからアップロード"
+    create-folder: "フォルダー作る"
+    upload: "ファイル上げる"
+    url-upload: "URLつこうて上げる"
 desktop/views/components/media-image.vue:
   sensitive: "ちょっと見せられへんわ"
   click-to-show: "クリックして見せるで"
@@ -518,30 +555,30 @@ desktop/views/components/media-video.vue:
   sensitive: "ちょっと見せられへんわ"
   click-to-show: "クリックして見せるで"
 desktop/views/components/follow-button.vue:
-  following: "フォロー中"
+  following: "フォローしとる"
   follow: "フォロー"
-  request-pending: "フォロー許可待ち"
-  follow-request: "フォロー申請"
+  request-pending: "フォローの許し待っとる"
+  follow-request: "フォロー許してくれや!言うてみる"
 desktop/views/components/followers-window.vue:
   followers: "{} のフォロワー"
 desktop/views/components/followers.vue:
-  empty: "フォロワーはいないようです。"
+  empty: "フォロワーはおらんっぽいで、知らんけど。"
 desktop/views/components/following-window.vue:
   following: "{} のフォロー"
 desktop/views/components/following.vue:
-  empty: "フォロー中のユーザーはいないようです。"
+  empty: "フォロー中のユーザーはおらんっぽいで、知らんけど。"
 desktop/views/components/friends-maker.vue:
-  title: "気になるユーザーをフォロー:"
-  empty: "おすすめのユーザーは見つかりませんでした。"
-  fetching: "読み込んでいます"
-  refresh: "もっと見る"
+  title: "おもろそうやな:"
+  empty: "おもろいユーザー居らんかったわ"
+  fetching: "読みこんどるで…"
+  refresh: "もっとあるやろ!"
   close: "閉じる"
 desktop/views/components/game-window.vue:
   game: "ゲーム"
 desktop/views/components/home.vue:
   done: "完了"
-  add-widget: "ウィジェットを追加:"
-  add: "追加"
+  add-widget: "ウィジェット増やす"
+  add: "増やす"
 desktop/views/input-dialog.vue:
   cancel: "やめとくわ"
   ok: "決定"
@@ -550,32 +587,30 @@ desktop/views/components/messaging-room-window.vue:
 desktop/views/components/messaging-window.vue:
   title: "メッセージ"
 desktop/views/components/note-detail.vue:
-  more: "会話をもっと読み込む"
-  private: "この投稿は非公開です"
-  deleted: "この投稿は削除されました"
+  more: "もっと会話あるやろ!"
+  private: "この投稿は見せられへんわ"
+  deleted: "この投稿なんか無くなってもうたわ"
   reposted-by: "{}がRenote"
-  location: "位置情報"
+  location: "ここおるで:"
   renote: "Renote"
   add-reaction: "リアクション"
 desktop/views/components/notes.note.vue:
   reposted-by: "{}がRenote"
-  reply: "返信"
+  reply: "返す"
   renote: "Renote"
   add-reaction: "リアクション"
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
-  error: "読み込みに失敗しました。"
-  retry: "リトライ"
-  load-more: "もっと読み込む"
+  error: "あかん、読み込めへんわ"
+  retry: "もっぺん"
+  load-more: "もっとあらへんのか!"
 desktop/views/components/notifications.vue:
-  more: "もっと見る"
-  empty: "ありません!"
+  more: "もっとあるやろ!"
+  empty: "あらへん!"
 desktop/views/components/post-form.vue:
-  add-visible-user: "+ユーザーを追加"
+  add-visible-user: "+ユーザー増やす"
   attach-location-information: "いる場所くっつけるで"
   hide-contents: "内容を隠す"
   reply-placeholder: "この投稿への返信..."
@@ -585,13 +620,13 @@ desktop/views/components/post-form.vue:
   renote: "Renote"
   posted: "投稿したで!"
   replied: "返信したで!"
-  reposted: "Renoteしました!"
+  reposted: "Renoteしたで!"
   note-failed: "投稿に失敗したで"
   reply-failed: "返信に失敗したで"
-  renote-failed: "Renoteに失敗しました"
+  renote-failed: "Renoteでけへん"
   posting: "投稿中"
-  attach-media-from-local: "PCからメディアを添付"
-  attach-media-from-drive: "ドライブからメディアを添付"
+  attach-media-from-local: "PCからメディア持ってくる"
+  attach-media-from-drive: "ドライブからメディア持ってくる"
   attach-cancel: "くっつけるのやめよか"
   insert-a-kao: "v('ω')v"
   create-poll: "アンケートを作成"
@@ -602,16 +637,16 @@ desktop/views/components/post-form.vue:
   geolocation-alert: "あんさんのつことる端末は位置情報に対応しとらんみたいやわ、知らんけど。"
   error: "エラー"
   enter-username: "ユーザー名を入力してや"
-  annotations: "内容への注釈 (オプション)"
+  annotations: "もっと教えてな(別にええけど)"
 desktop/views/components/post-form-window.vue:
   note: "新規投稿"
-  reply: "返信"
+  reply: "返す"
   attaches: "添付: {}メディア"
-  uploading-media: "{}個のメディアをアップロード中"
+  uploading-media: "{}個のメディアを上げてるで…"
 desktop/views/components/progress-dialog.vue:
-  waiting: "待機中"
+  waiting: "待っとる"
 desktop/views/components/renote-form.vue:
-  quote: "引用する..."
+  quote: "持ってくる…"
   cancel: "やめとくわ"
   renote: "Renote"
   reposting: "しています..."
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -645,37 +683,38 @@ desktop/views/components/settings.vue:
   customize: "ホームをカスタマイズ"
   choose-wallpaper: "壁紙を選択"
   delete-wallpaper: "壁紙を削除"
-  dark-mode: "ダークモード"
-  circle-icons: "円形のアイコンを使用"
-  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
-  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
-  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
-  show-clock-on-header: "右上に時計を表示する"
-  show-reply-target: "リプライ先を表示する"
-  show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
-  show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する"
-  show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
-  show-maps: "マップの自動展開"
-  show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
+  dark-mode: "夜にすんで"
+  circle-icons: "アイコンもタコ焼きも丸いやんな?"
+  contrasted-acct: "ユーザー名ようわからんし見やすしといて"
+  gradient-window-header: "ウィンドウのタイトルバーにグラデーション付ける"
+  post-form-on-timeline: "タイムラインの上の方で投稿できるようにせえへん?"
+  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示すんで"
+  show-clock-on-header: "右上をカリヨン広場にする(時計表示)"
+  show-reply-target: "どこにリプライするんや見せて"
+  show-my-renotes: "わしのRenoteもタイムライン載せてくれや"
+  show-renoted-my-notes: "わしのRenoteもタイムライン載せてくれや"
+  show-local-renotes: "ローカル投稿のRenoteも見たいんや"
+  show-maps: "地図勝手にバァーって開いてくれ"
+  show-maps-desc: "どこにおるんかわかっとる投稿の地図は自動で見せるで"
   sound: "サウンド"
-  enable-sounds: "サウンドを有効にする"
-  enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
+  enable-sounds: "サウンド鳴らす"
+  enable-sounds-desc: "投稿やメッセージもろたとき、音鳴らしたるわ。大丈夫や、この設定はブラウザが覚えてくれとる。"
   volume: "ボリューム"
   test: "テスト"
   mobile: "モバイル"
-  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
+  disable-via-mobile: "「モバイルからの投稿」フラグなんて要らんわ"
   language: "言語"
-  pick-language: "言語を選択"
-  recommended: "推奨"
+  pick-language: "言語選んでや"
+  recommended: "おすすめ"
   auto: "自動"
-  specify-language: "言語を指定"
-  language-desc: "変更はページの再度読み込み後に反映されます。"
+  specify-language: "言語選んでくれ"
+  language-desc: "変更はページの再度読み込み後に反映されんで。"
   cache: "キャッシュ"
-  clean-cache: "クリーンアップ"
-  cache-warn: "クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。"
-  cache-cleared: "キャッシュを削除しました"
-  cache-cleared-desc: "ページを再度読み込みしてください。"
-  auto-watch: "投稿の自動ウォッチ"
+  clean-cache: "お掃除"
+  cache-warn: "お掃除するとな、ブラウザが覚えてくれとるアカウントのあれこれや書きかけの投稿・返信・メッセージや設定情報なんかのデータが全部飛んでいくんや。これやったらページ再読込しといてな。"
+  cache-cleared: "キャッシュお掃除したで"
+  cache-cleared-desc: "もっぺんページ読みこみ直してくれや"
+  auto-watch: "投稿勝手にウォッチしといてや"
   auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。"
   about: "Misskeyについて"
   operator: "このサーバーの運営者"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "サインイン中…"
   signup-button: "サインアップ"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "ドライブ"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "べっぴんさん"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index a4d91c5ef0..69ded56a7a 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskey를 찾아 주셔서 감사합니다. Misskey은 지구에서 태어난 <b>분산 마이크로 블로그 SNS </b> 입니다. Fediverse (다양한 SNS로 구성되는 우주)에 존재하는 다른 SNS와 상호 연결되어 있습니다. 잠시 도시의 번잡함에서 벗어나 새로운 인터넷에 다이브 해 보지 않겠습니까."
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "광고 차단기를 해제하십시오"
     warning: "<strong>Misskey는 광고를 게재하지 않습니다</strong> 그러나 광고를 차단하는 기능 기능을 사용할 경우 일부 기능을 사용할 수 없게 될 가능성이나 결함이 발생하는 경우가 있습니다."
@@ -68,6 +81,15 @@ common:
     confused: "곤란하고 있어"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "지금 어떻게하고있어?"
     b: "뭔가 있었습니까?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "게시물의 문자 애니메이션을 비활성화 할"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "무승부"
     my-turn: "당신의 차례입니다"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
   flush: "キャッシュの削除"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "당신"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "削除"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
   retry: "リトライ"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
new file mode 100644
index 0000000000..137502c91e
--- /dev/null
+++ b/locales/nl-NL.yml
@@ -0,0 +1,1287 @@
+---
+meta:
+  lang: "Nederlands"
+  divider: ""
+common:
+  misskey: "Deel alles met anderen die ook Misskey gebruiken."
+  about-title: "A ⭐ of fediverse."
+  about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
+  adblock:
+    detected: "広告ブロッカーを無効にしてください"
+    warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
+  application-authorization: "アプリの連携"
+  close: "閉じる"
+  do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
+  got-it: "わかった"
+  customization-tips:
+    title: "カスタマイズのヒント"
+    paragraph1: "ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。"
+    paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。"
+    paragraph3: "ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。"
+    paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
+    gotit: "Got it!"
+  notification:
+    file-uploaded: "ファイルがアップロードされました"
+    message-from: "{}さんからメッセージ:"
+    reversi-invited: "対局への招待があります"
+    reversi-invited-by: "{}さんから"
+    notified-by: "{}さんから"
+    reply-from: "{}さんから返信:"
+    quoted-by: "{}さんが引用:"
+  time:
+    unknown: "onbekend"
+    future: "toekomstig"
+    just_now: "zojuist"
+    seconds_ago: "{}s geleden"
+    minutes_ago: "{}m geleden"
+    hours_ago: "{}u geleden"
+    days_ago: "{}d geleden"
+    weeks_ago: "{}week/weken geleden"
+    months_ago: "{}maand(en) geleden"
+    years_ago: "{}jaar geleden"
+  month-and-day: "{month}月 {day}日"
+  trash: "ゴミ箱"
+  weekday-short:
+    sunday: "Z"
+    monday: "M"
+    tuesday: "D"
+    wednesday: "W"
+    thursday: "D"
+    friday: "V"
+    saturday: "Z"
+  weekday:
+    sunday: "日曜日"
+    monday: "月曜日"
+    tuesday: "火曜日"
+    wednesday: "水曜日"
+    thursday: "木曜日"
+    friday: "金曜日"
+    saturday: "土曜日"
+  reactions:
+    like: "Leuk"
+    love: "Geweldig"
+    laugh: "Grappig"
+    hmm: "Eh...?"
+    surprise: "Wauw"
+    congrats: "Gefeliciteerd!"
+    angry: "Boos"
+    confused: "Verward"
+    rip: "RIP"
+    pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
+  note-placeholders:
+    a: "今どうしてる?"
+    b: "何かありましたか?"
+    c: "何をお考えですか?"
+    d: "言いたいことは?"
+    e: "ここに書いてください"
+    f: "あなたが書くのを待っています..."
+  search: "検索"
+  delete: "Verwijderen"
+  loading: "Bezig met laden"
+  ok: "Oké"
+  update-available-title: "更新があります"
+  update-available: "Er is een nieuwe versie van Misskey beschikbaar: {newer} (de huidige versie is {current}). Herlaad de pagina om de update toe te passen."
+  my-token-regenerated: "Je sleutel is gegenereerd; je wordt nu uitgelogd."
+  i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
+  show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
+  use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
+  verified-user: "公式アカウント"
+  disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
+  reversi:
+    drawn: "引き分け"
+    my-turn: "あなたのターンです"
+    opponent-turn: "相手のターンです"
+    turn-of: "{}のターンです"
+    past-turn-of: "{}のターン"
+    won: "{}の勝ち"
+    black: "黒"
+    white: "白"
+    total: "合計"
+    this-turn: "{}ターン目"
+  widgets:
+    analog-clock: "アナログ時計"
+    profile: "プロフィール"
+    calendar: "カレンダー"
+    timemachine: "カレンダー(タイムマシン)"
+    activity: "アクティビティ"
+    rss: "RSSリーダー"
+    memo: "付箋"
+    trends: "トレンド"
+    photo-stream: "フォトストリーム"
+    posts-monitor: "投稿チャート"
+    slideshow: "スライドショー"
+    version: "バージョン"
+    broadcast: "ブロードキャスト"
+    notifications: "通知"
+    users: "おすすめユーザー"
+    polls: "アンケート"
+    post-form: "投稿フォーム"
+    messaging: "メッセージ"
+    server: "サーバー情報"
+    donation: "寄付のお願い"
+    nav: "ナビゲーション"
+    tips: "ヒント"
+    hashtags: "ハッシュタグ"
+  deck:
+    widgets: "ウィジェット"
+    home: "ホーム"
+    local: "ローカル"
+    hybrid: "ソーシャル"
+    global: "グローバル"
+    notifications: "通知"
+    list: "リスト"
+    swap-left: "左に移動"
+    swap-right: "右に移動"
+    swap-up: "上に移動"
+    swap-down: "下に移動"
+    remove: "カラムを削除"
+    add-column: "カラムを追加"
+    rename: "名前を変更"
+    stack-left: "左に重ねる"
+    pop-right: "右に出す"
+auth/views/form.vue:
+  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
+  permission-ask: "このアプリは次の権限を要求しています:"
+  account-read: "アカウントの情報を見る。"
+  account-write: "アカウントの情報を操作する。"
+  note-write: "投稿する。"
+  like-write: "いいねしたりいいね解除する。"
+  following-write: "フォローしたりフォロー解除する。"
+  drive-read: "ドライブを見る。"
+  drive-write: "ドライブを操作する。"
+  notification-read: "通知を見る。"
+  notification-write: "通知を操作する。"
+  cancel: "キャンセル"
+  accept: "アクセスを許可"
+auth/views/index.vue:
+  loading: "読み込み中"
+  denied: "アプリケーションの連携をキャンセルしました。"
+  denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。"
+  already-authorized: "このアプリは既に連携済みです"
+  allowed: "アプリケーションの連携を許可しました"
+  callback-url: "アプリケーションに戻っています"
+  please-go-back: "アプリケーションに戻って、やっていってください。"
+  error: "セッションが存在しません。"
+  sign-in: "サインインしてください"
+common/views/components/games/reversi/reversi.vue:
+  matching:
+    waiting-for: "{}を待っています"
+    cancel: "キャンセル"
+common/views/components/games/reversi/reversi.game.vue:
+  surrender: "投了"
+  surrendered: "投了により"
+  is-llotheo: "石の少ない方が勝ち(ロセオ)"
+  looped-map: "ループマップ"
+  can-put-everywhere: "どこでも置けるモード"
+common/views/components/games/reversi/reversi.index.vue:
+  title: "Misskey Reversi"
+  sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
+  invite: "招待"
+  rule: "遊び方"
+  rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
+  mode-invite: "招待"
+  mode-invite-desc: "指定したユーザーと対戦するモードです。"
+  invitations: "対局の招待があります!"
+  my-games: "自分の対局"
+  all-games: "みんなの対局"
+  enter-username: "ユーザー名を入力してください"
+  game-state:
+    ended: "終了"
+    playing: "進行中"
+common/views/components/games/reversi/reversi.room.vue:
+  settings-of-the-game: "ゲームの設定"
+  choose-map: "マップを選択"
+  random: "ランダム"
+  black-or-white: "先手/後手"
+  black-is: "{}が黒"
+  rules: "ルール"
+  is-llotheo: "石の少ない方が勝ち(ロセオ)"
+  looped-map: "ループマップ"
+  can-put-everywhere: "どこでも置けるモード"
+  settings-of-the-bot: "Botの設定"
+  this-game-is-started-soon: "ゲームは数秒後に開始されます"
+  waiting-for-other: "相手の準備が完了するのを待っています"
+  waiting-for-me: "あなたの準備が完了するのを待っています"
+  waiting-for-both: "準備中"
+  cancel: "キャンセル"
+  ready: "準備完了"
+  cancel-ready: "準備続行"
+common/views/components/connect-failed.vue:
+  title: "Verbinden met server mislukt"
+  description: "Er is een probleem met je internetverbinding, de server ligt plat of er wordt aan gewerkt. {Probeer} het later opnieuw."
+  thanks: "Bedankt voor het gebruiken van Misskey."
+  troubleshoot: "Probleemoplossing"
+common/views/components/connect-failed.troubleshooter.vue:
+  title: "Probleemoplossing"
+  network: "Netwerkverbinding"
+  checking-network: "Bezig met controleren van netwerkverbinding"
+  internet: "Internetverbinding"
+  checking-internet: "Bezig met controleren van internetverbinding"
+  server: "Serververbinding"
+  checking-server: "Bezig met controleren van serververbinding"
+  finding: "Bezig met vaststellen van probleem"
+  no-network: "Er is geen internetverbinding"
+  no-network-desc: "Zorg ervoor dat je verbonden bent met een netwerk."
+  no-internet: "Er is geen internetverbinding"
+  no-internet-desc: "Zorg ervoor dat je verbonden bent met het internet."
+  no-server: "Verbinden met Misskey-server mislukt"
+  no-server-desc: "De netwerkverbinding van je computer is goed, maar er kan geen verbinding worden gemaakt met de Misskey-server. Het kan dat de server plat ligt of dat eraan wordt gewerkt. Probeer het later opnieuw."
+  success: "Verbonden met de Misskey-server"
+  success-desc: "Het verbinden lijkt te lukken. Herlaad de pagina."
+  flush: "Cache leegmaken"
+  set-version: "Versie opgeven"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
+common/views/components/messaging.vue:
+  search-user: "Gebruiker zoeken"
+  you: "Jij"
+  no-history: "Geen geschiedenis"
+common/views/components/messaging-room.vue:
+  empty: "Geen gesprekken"
+  more: "Meer"
+  no-history: "Er is geen verdere geschiedenis"
+  resize-form: "Versleep om grootte te wijzigen"
+  new-message: "Nieuw bericht"
+  only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
+common/views/components/messaging-room.form.vue:
+  input-message-here: "Voer hier je bericht in"
+  send: "Versturen"
+  attach-from-local: "Bestanden bijvoegen van je computer"
+  attach-from-drive: "Bestanden bijvoegen van je Drive"
+  only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
+common/views/components/messaging-room.message.vue:
+  is-read: "Gelezen"
+  deleted: "Dit bericht is verwijderd"
+common/views/components/nav.vue:
+  about: "Over"
+  stats: "Statistieken"
+  status: "Status"
+  wiki: "Wiki"
+  donors: "Donateurs"
+  repository: "Broncode"
+  develop: "Ontwikkelaars"
+  feedback: "Feedback"
+common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
+  favorite: "Deze notitie toevoegen aan favorieten"
+  pin: "Vastmaken aan profielpagina"
+  delete: "削除"
+  delete-confirm: "この投稿を削除しますか?"
+  remote: "Origineel tonen"
+common/views/components/poll.vue:
+  vote-to: "Stemmen op '{}'"
+  vote-count: "{} stemmen"
+  total-users: "{} gebruikers hebben gestemd"
+  vote: "Stemmen"
+  show-result: "Resultaten tonen"
+  voted: "Gestemd"
+common/views/components/poll-editor.vue:
+  no-only-one-choice: "Je moet twee of meer keuzes invoeren."
+  choice-n: "Keuze {}"
+  remove: "Deze keuze verwijderen"
+  add: "+ Keuze toevoegen"
+  destroy: "Deze peiling vernietigen"
+common/views/components/reaction-picker.vue:
+  choose-reaction: "Kies een reactie"
+common/views/components/signin.vue:
+  username: "Gebruikersnaam"
+  password: "Wachtwoord"
+  token: "Sleutel"
+  signing-in: "Bezig met inloggen..."
+  signin: "Inloggen"
+  or: "または"
+  signin-with-twitter: "Twitterでログイン"
+  login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
+common/views/components/signup.vue:
+  invitation-code: "招待コード"
+  invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
+  username: "Gebruikersnaam"
+  checking: "Bezig met controleren..."
+  available: "Beschikbaar"
+  unavailable: "Niet beschikbaar"
+  error: "Netwerkfout"
+  invalid-format: "Gebruik alleen letters, cijfers en -."
+  too-short: "Voer minimaal 1 teken in!"
+  too-long: "Voer maximaal 20 tekens in."
+  password: "Wachtwoord"
+  password-placeholder: "Wij raden aan meer dan 8 tekens te gebruiken."
+  weak-password: "Zwak"
+  normal-password: "'t Ken net"
+  strong-password: "Sterk"
+  retype: "Opnieuw invoeren"
+  retype-placeholder: "Wachtwoord bevestigen"
+  password-matched: "Oké"
+  password-not-matched: "Komt niet overeen"
+  recaptcha: "Verifiëren"
+  create: "Account creëren"
+  some-error: "Het creëren van een account is mislukt. Probeer het opnieuw."
+common/views/components/special-message.vue:
+  new-year: "Gelukkig nieuwjaar!"
+  christmas: "Fijne kerstdagen!"
+common/views/components/stream-indicator.vue:
+  connecting: "Bezig met verbinden"
+  reconnecting: "Bezig met herverbinden"
+  connected: "Verbonden"
+common/views/components/twitter-setting.vue:
+  description: "Als je je Twitter-account verbindt met je Misskey-account, dan kun je je Twitter-accountinformatie terugzien op je profiel en kun je inloggen met Twitter."
+  connected-to: "Je bent verbonden met dit Twitter-account"
+  detail: "Details..."
+  reconnect: "Opnieuw verbinden"
+  connect: "Koppel je Twitter-account"
+  disconnect: "Verbinding verbreken"
+common/views/components/uploader.vue:
+  waiting: "Bezig met wachten"
+common/views/components/visibility-chooser.vue:
+  public: "公開"
+  home: "ホーム"
+  home-desc: "ホームタイムラインにのみ公開"
+  followers: "フォロワー"
+  followers-desc: "自分のフォロワーにのみ公開"
+  specified: "ダイレクト"
+  specified-desc: "指定したユーザーにのみ公開"
+  private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
+common/views/widgets/broadcast.vue:
+  fetching: "Bezig met ophalen"
+  no-broadcasts: "Geen uitzendingen"
+  have-a-nice-day: "Fijne dag!"
+  next: "Volgende"
+common/views/widgets/calendar.vue:
+  year: "{}年"
+  month: "{}月"
+  day: "{}日"
+  today: "今日:"
+  this-month: "今月:"
+  this-year: "今年:"
+common/views/widgets/donation.vue:
+  title: "Donatie"
+  text: "Om Misskey draaiende te houden, geven we geld uit aan onze domeinnaam, servers, enz. We maken hier geen winst op, dus we zouden het fijn vinden als je een donatie wilt doen. Neem in dat geval contact op via {}. Bedankt voor je bijdrage!"
+common/views/widgets/photo-stream.vue:
+  title: "Fotostream"
+  no-photos: "Geen foto's"
+common/views/widgets/posts-monitor.vue:
+  title: "投稿チャート"
+  toggle: "表示を切り替え"
+common/views/widgets/hashtags.vue:
+  title: "ハッシュタグ"
+common/views/widgets/server.vue:
+  title: "Serverinformatie"
+  toggle: "Schakelen tussen weergaven"
+common/views/widgets/memo.vue:
+  title: "付箋"
+  memo: "ここに書いて!"
+  save: "保存"
+common/views/widgets/slideshow.vue:
+  folder-customize-mode: "フォルダを指定するには、カスタマイズモードを終了してください"
+  folder: "クリックしてフォルダを指定してください"
+  no-image: "このフォルダには画像がありません"
+common/views/widgets/tips.vue:
+  tips-line1: "<kbd>t</kbd>でタイムラインにフォーカスできます"
+  tips-line2: "<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます"
+  tips-line3: "投稿フォームにはファイルをドラッグ&ドロップできます"
+  tips-line4: "投稿フォームにクリップボードにある画像データをペーストできます"
+  tips-line5: "ドライブにファイルをドラッグ&ドロップしてアップロードできます"
+  tips-line6: "ドライブでファイルをドラッグしてフォルダ移動できます"
+  tips-line7: "ドライブでフォルダをドラッグしてフォルダ移動できます"
+  tips-line8: "ホームは設定からカスタマイズできます"
+  tips-line9: "MisskeyはAGPLv3です"
+  tips-line10: "タイムマシンウィジェットを利用すると、簡単に過去のタイムラインに遡れます"
+  tips-line11: "投稿の ... をクリックして、投稿をユーザーページにピン留めできます"
+  tips-line13: "投稿に添付したファイルは全てドライブに保存されます"
+  tips-line14: "ホームのカスタマイズ中、ウィジェットを右クリックしてデザインを変更できます"
+  tips-line17: "「**」でテキストを囲むと**強調表示**されます"
+  tips-line19: "いくつかのウィンドウはブラウザの外に切り離すことができます"
+  tips-line20: "カレンダーウィジェットのパーセンテージは、経過の割合を示しています"
+  tips-line21: "APIを利用してbotの開発なども行えます"
+  tips-line23: "まゆかわいいよまゆ"
+  tips-line24: "Misskeyは2014年にサービスを開始しました"
+  tips-line25: "対応ブラウザではMisskeyを開いていなくても通知を受け取れます"
+common/views/pages/follow.vue:
+  signed-in-as: "{}としてサインイン中"
+  following: "フォロー中"
+  follow: "フォロー"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+desktop:
+  banner-crop-title: "バナーとして表示する部分を選択"
+  banner: "バナー"
+  uploading-banner: "新しいバナーをアップロードしています"
+  banner-updated: "バナーを更新しました"
+  choose-banner: "バナーにする画像を選択"
+  avatar-crop-title: "アバターとして表示する部分を選択"
+  avatar: "アバター"
+  uploading-avatar: "新しいアバターをアップロードしています"
+  avatar-updated: "アバターを更新しました"
+  choose-avatar: "アバターにする画像を選択"
+  invalid-filetype: "この形式のファイルはサポートされていません"
+desktop/views/components/activity.chart.vue:
+  total: "Zwart ... totaal"
+  notes: "Blauw ... notities"
+  replies: "Rood ... antwoorden"
+  renotes: "Groen ... gedeelde notities"
+desktop/views/components/activity.vue:
+  title: "Activiteit"
+  toggle: "Schakelen tussen weergaven"
+desktop/views/components/calendar.vue:
+  title: "{1} / {2}"
+  prev: "Vorige maand"
+  next: "Volgende maand"
+  go: "Klik om te navigeren"
+desktop/views/components/charts.vue:
+  title: "チャート"
+  per-day: "1日ごと"
+  per-hour: "1時間ごと"
+  notes: "投稿"
+  users: "ユーザー"
+  drive: "ドライブ"
+  network: "ネットワーク"
+  charts:
+    notes: "投稿の増減 (統合)"
+    local-notes: "投稿の増減 (ローカル)"
+    remote-notes: "投稿の増減 (リモート)"
+    notes-total: "投稿の累計"
+    users: "ユーザーの増減"
+    users-total: "ユーザーの累計"
+    drive: "ドライブ使用量の増減"
+    drive-total: "ドライブ使用量の累計"
+    drive-files: "ドライブのファイル数の増減"
+    drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
+desktop/views/components/choose-file-from-drive-window.vue:
+  choose-file: "Bestanden kiezen"
+  upload: "Bestanden uploaden van je computer"
+  cancel: "Annuleren"
+  ok: "Oké"
+  choose-prompt: "Kies een bestand"
+desktop/views/components/choose-folder-from-drive-window.vue:
+  cancel: "Annuleren"
+  ok: "Oké"
+  choose-prompt: "Kies een map"
+desktop/views/components/crop-window.vue:
+  skip: "Bijsnijden overslaan"
+  cancel: "Annuleren"
+  ok: "Oké"
+desktop/views/components/drive-window.vue:
+  used: "gebruikt"
+  drive: "Drive"
+desktop/views/components/drive.file.vue:
+  avatar: "Gebruikersafbeelding"
+  banner: "Omslagfoto"
+  nsfw: "閲覧注意"
+  contextmenu:
+    rename: "Naam wijzigen"
+    mark-as-sensitive: "閲覧注意に設定"
+    unmark-as-sensitive: "閲覧注意を解除"
+    copy-url: "URL kopiëren"
+    download: "Downloaden"
+    else-files: "Overig"
+    set-as-avatar: "Instellen als gebruikersafbeelding"
+    set-as-banner: "Instellen als omslagfoto"
+    open-in-app: "Openen in app"
+    add-app: "App toevoegen"
+    rename-file: "Bestandsnaam wijzigen"
+    input-new-file-name: "Voer een nieuwe naam in"
+    copied: "Gekopieerd"
+    copied-url-to-clipboard: "URL gekopieerd naar klembord"
+desktop/views/components/drive.folder.vue:
+  unable-to-process: "De operatie kan niet worden voltooid."
+  circular-reference-detected: "De bestemmingsmap is een submap van de map die je wilt verplaatsen."
+  unhandled-error: "Onbekende fout"
+  contextmenu:
+    move-to-this-folder: "Verplaatsen naar deze map"
+    show-in-new-window: "Openen in nieuw venster"
+    rename: "Naam wijzigen"
+    rename-folder: "Mapnaam wijzigen"
+    input-new-folder-name: "Voer een nieuwe naam in"
+desktop/views/components/drive.nav-folder.vue:
+  drive: "Drive"
+desktop/views/components/drive.vue:
+  search: "Zoeken"
+  load-more: "Meer laden"
+  empty-draghover: "Welkom!"
+  empty-drive: "Je schijf is leeg"
+  empty-drive-description: "Je kunt ook uploaden door te klikken met de rechtermuisknop en te kiezen voor \"Bestand uploaden\" of door een bestand naar dit venster te slepen."
+  empty-folder: "Deze map is leeg"
+  unable-to-process: "De operatie kan niet worden voltooid."
+  circular-reference-detected: "De bestemmingsmap is een submap van de te verplaatsen map."
+  unhandled-error: "Onbekende fout"
+  url-upload: "Uploaden via URL"
+  url-of-file: "URL van het te uploaden bestand"
+  url-upload-requested: "Uploadverzoek"
+  may-take-time: "Het kan even duren voordat het uploaden voltooid is."
+  create-folder: "Map creëren"
+  folder-name: "Mapnaam"
+  contextmenu:
+    create-folder: "Map creëren"
+    upload: "Bestand uploaden"
+    url-upload: "Uploaden via URL"
+desktop/views/components/media-image.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+desktop/views/components/media-video.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+desktop/views/components/follow-button.vue:
+  following: "フォロー中"
+  follow: "Volgen"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+desktop/views/components/followers-window.vue:
+  followers: "Volgers van {}"
+desktop/views/components/followers.vue:
+  empty: "Het lijkt erop dat je geen volgers hebt."
+desktop/views/components/following-window.vue:
+  following: "Volgend {}"
+desktop/views/components/following.vue:
+  empty: "Je volgt niemand."
+desktop/views/components/friends-maker.vue:
+  title: "Aanbevolen gebruikers:"
+  empty: "Er zijn geen vergelijkbare gebruikers gevonden."
+  fetching: "Bezig met laden…"
+  refresh: "Meer"
+  close: "Sluiten"
+desktop/views/components/game-window.vue:
+  game: "Othello"
+desktop/views/components/home.vue:
+  done: "Versturen"
+  add-widget: "Widget toevoegen:"
+  add: "Toevoegen"
+desktop/views/input-dialog.vue:
+  cancel: "Annuleren"
+  ok: "Oké"
+desktop/views/components/messaging-room-window.vue:
+  title: "Berichten:"
+desktop/views/components/messaging-window.vue:
+  title: "Gesprekken"
+desktop/views/components/note-detail.vue:
+  more: "Meer gesprekken laden"
+  private: "(dit bericht is privé)"
+  deleted: "この投稿は削除されました"
+  reposted-by: "{}がRenote"
+  location: "Locatie"
+  renote: "Renote"
+  add-reaction: "リアクション"
+desktop/views/components/notes.note.vue:
+  reposted-by: "Hergeplaatst door {}"
+  reply: "Antwoord"
+  renote: "Renote"
+  add-reaction: "Reactie toevoegen"
+  detail: "Details tonen"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+desktop/views/components/notes.vue:
+  error: "Laden mislukt."
+  retry: "Opnieuw proberen"
+  load-more: "もっと読み込む"
+desktop/views/components/notifications.vue:
+  more: "Meer"
+  empty: "Geen meldingen"
+desktop/views/components/post-form.vue:
+  add-visible-user: "+ユーザーを追加"
+  attach-location-information: "位置情報を添付する"
+  hide-contents: "内容を隠す"
+  reply-placeholder: "Deze notitie beantwoorden..."
+  quote-placeholder: "Deze notitie citeren..."
+  submit: "投稿"
+  reply: "Beantwoorden"
+  renote: "Renote"
+  posted: "Geplaatst!"
+  replied: "Beantwoord!"
+  reposted: "Hergeplaatst!"
+  note-failed: "Noteren mislukt"
+  reply-failed: "Beantwoorden mislukt"
+  renote-failed: "Renote mislukt"
+  posting: "Bezig met plaatsen"
+  attach-media-from-local: "Media bijvoegen van je computer"
+  attach-media-from-drive: "Media bijvoegen uit je Drive"
+  attach-cancel: "Bijlage annuleren"
+  insert-a-kao: "v(‘ω’)v"
+  create-poll: "Peiling creëren"
+  text-remain: "{} resterende tekens"
+  recent-tags: "最近"
+  click-to-tagging: "クリックでタグ付け"
+  visibility: "公開範囲"
+  geolocation-alert: "お使いの端末は位置情報に対応していません"
+  error: "エラー"
+  enter-username: "ユーザー名を入力してください"
+  annotations: "内容への注釈 (オプション)"
+desktop/views/components/post-form-window.vue:
+  note: "Nieuwe notitie"
+  reply: "Beantwoorden"
+  attaches: "{} media bijgevoegd"
+  uploading-media: "Bezig met uploaden van media {}"
+desktop/views/components/progress-dialog.vue:
+  waiting: "Bezig met wachten"
+desktop/views/components/renote-form.vue:
+  quote: "Citeren..."
+  cancel: "Annuleren"
+  renote: "Renote"
+  reposting: "Bezig met herplaatsen..."
+  success: "Hergeplaatst!"
+  failure: "Renote mislukt"
+desktop/views/components/renote-form-window.vue:
+  title: "Weet je zeker dat je deze notitie wilt renoten?"
+desktop/views/components/settings-window.vue:
+  settings: "設定"
+desktop/views/components/settings.vue:
+  profile: "Profiel"
+  notification: "Melding"
+  apps: "Apps"
+  mute: "Dempen"
+  drive: "Drive"
+  security: "Beveiliging"
+  signin: "Inloggeschiedenis"
+  password: "Wachtwoord"
+  2fa: "Authenticatie in twee stappen"
+  other: "Overig"
+  license: "Licentie"
+  behaviour: "Gedrag"
+  fetch-on-scroll: "Ophalen bij scrollen"
+  fetch-on-scroll-desc: "Als je omlaag scrolt, wordt de rest van de inhoud automatisch opgehaald."
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
+  auto-popout: "Venster automatisch uitvouwen"
+  auto-popout-desc: "Venster uitvouwen, indien mogelijk. Deze instelling wordt opgeslagen in je browser."
+  advanced: "Geavanceerde instellingen"
+  api-via-stream: "API-verzoek via stream"
+  api-via-stream-desc: "API-verzoek wordt uitgevoerd via de WebSocket-verbinding i.p.v. de ingebouwde ophaal-API (voor verbeterde prestaties). Deze instelling wordt opgeslagen in je browser."
+  display: "Ontwerp en weergave"
+  customize: "Startpagina aanpassen"
+  choose-wallpaper: "壁紙を選択"
+  delete-wallpaper: "壁紙を削除"
+  dark-mode: "Donkere modus"
+  circle-icons: "Ronde pictogrammen gebruiken"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
+  gradient-window-header: "Kleurverloop gebruiken op vensterkoppen"
+  post-form-on-timeline: "Berichtformulier boven de tijdlijn tonen"
+  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
+  show-clock-on-header: "右上に時計を表示する"
+  show-reply-target: "Antwoord-knop tonen"
+  show-my-renotes: "Mijn renote tonen op de tijdlijn"
+  show-renoted-my-notes: "Mijn gerenote bericht tonen op de tijdlijn"
+  show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
+  show-maps: "Kaart tonen"
+  show-maps-desc: "Kaart van bijgevoegde locatie tonen."
+  sound: "Geluid"
+  enable-sounds: "Geluid inschakelen"
+  enable-sounds-desc: "Een geluid afspelen bij het ontvangen van een bericht. Deze instelling wordt opgeslagen in je browser."
+  volume: "Volume"
+  test: "Testen"
+  mobile: "Mobiel"
+  disable-via-mobile: "Berichten niet markeren als 'via mobiel'"
+  language: "Taal"
+  pick-language: "Selecteer een taal"
+  recommended: "Aanbevolen"
+  auto: "Automatisch"
+  specify-language: "Taal opgeven"
+  language-desc: "Je moet de pagina herladen om de wijzigingen toe te passen."
+  cache: "Cache"
+  clean-cache: "Opschonen"
+  cache-warn: "De cache van je accountinformatie/berichten/antwoorden/instellingen wordt verwijderd. Je moet de pagina herladen na het opschonen."
+  cache-cleared: "Cache opgeschoond"
+  cache-cleared-desc: "Herlaad de pagina."
+  auto-watch: "Automatisch volgen"
+  auto-watch-desc: "Automatisch meldingen ontvangen over het bericht/antwoord."
+  about: "Over Misskey"
+  operator: "De administrator van deze instantie"
+  update: "Misskey-update"
+  version: "Versie:"
+  latest-version: "Nieuwste versie:"
+  update-checking: "Bezig met controleren op updates"
+  do-update: "Controleren op updates"
+  update-settings: "Geavanceerde instellingen"
+  prevent-update: "Updates uitstellen (niet aanbevolen)"
+  prevent-update-desc: "De instelling is alleen van toepassing op dit apparaat."
+  no-updates: "Geen updates beschikbaar"
+  no-updates-desc: "Je Misskey is bijgewerkt."
+  update-available: "Nieuwe versie beschikbaar!"
+  update-available-desc: "Herlaad de pagina om de updates toe te passen."
+  advanced-settings: "Geavanceerd"
+  debug-mode: "Foutopsporingsmodus inschakelen"
+  debug-mode-desc: "Deze instelling wordt opgeslagen in de browser."
+  experimental: "Experimentele functies inschakelen"
+  experimental-desc: "Dit maakt Misskey instabiel. Deze instelling wordt opgeslagen in de browser."
+  tools: "Hulpmiddelen"
+  task-manager: "Taakbeheer"
+  third-parties: "Derde partij"
+desktop/views/components/settings.2fa.vue:
+  intro: "Als je verificatie in twee stappen instelt, dan heb je niet alleen een wachtwoord nodig bij het inloggen, maar ook een geregistreerd fysiek apparaat (zoals je smartphone). Dit verhoogt de veiligheid. "
+  detail: "Details bekijken..."
+  url: "https://www.google.com/landing/2step/"
+  caution: "Als je geen toegang meer hebt tot je apparaat, dan kun je niet meer verbinden met Misskey!"
+  register: "Apparaat registreren"
+  already-registered: "Er is al een apparaat geregistreerd"
+  unregister: "Uitschakelen"
+  unregistered: "Authenticatie in twee stappen is uitgeschakeld."
+  enter-password: "Voer het wachtwoord in"
+  authenticator: "Installeer eerst Google Authenticator op je apparaat:"
+  howtoinstall: "Hoe installeer ik dit?"
+  scan: "Scan daarna de QR-code:"
+  done: "Voer de op je apparaat getoonde sleutel in:"
+  submit: "Versturen"
+  success: "Instellen voltooid!"
+  failed: "Instellen mislukt. Zorg ervoor dat de sleutel juist is."
+  info: "Vanaf nu moet je ook de op je apparaat getoonde sleutel tonen bij het inloggen op Misskey."
+desktop/views/components/settings.api.vue:
+  intro: "Als je toegang wilt tot de API, stel deze sleutel dan in als 'i' bij de verzoekparameters."
+  caution: "Laat deze sleutel niet zien aan derde partijen (en voer hem nergens anders in dan hier), anders kan je account gehackt worden."
+  regeneration-of-token: "Mocht deze sleutel tóch uitlekken, dan kun je hem opnieuw genereren."
+  regenerate-token: "Sleutel opnieuw genereren"
+  token: "Sleutel:"
+  enter-password: "Voer je wachtwoord in"
+desktop/views/components/settings.apps.vue:
+  no-apps: "連携しているアプリケーションはありません"
+desktop/views/components/settings.drive.vue:
+  max: "中"
+  in-use: "使用中"
+desktop/views/components/settings.mute.vue:
+  no-users: "Geen gedempte gebruikers"
+desktop/views/components/settings.password.vue:
+  reset: "Wachtwoord wijzigen"
+  enter-current-password: "Voer je huidige wachtwoord in"
+  enter-new-password: "Voer je nieuwe wachtwoord in"
+  enter-new-password-again: "Voer je nieuwe wachtwoord nogmaals in"
+  not-match: "Het nieuwe wachtwoord komt niet overeen"
+  changed: "Wachtwoord bijgewerkt"
+desktop/views/components/settings.profile.vue:
+  avatar: "Gebruikersafbeelding"
+  choice-avatar: "Kies een afbeelding"
+  name: "Naam"
+  location: "Locatie"
+  description: "Omschrijving"
+  birthday: "Geboortedatum"
+  save: "Profiel bijwerken"
+  locked-account: "アカウントの保護"
+  is-locked: "フォローを承認制にする"
+  other: "その他"
+  is-bot: "Dit account is een Bot"
+  is-cat: "Dit account is een Kat"
+  profile-updated: "プロフィールを更新しました"
+desktop/views/components/sub-note-content.vue:
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  media-count: "{}つのメディア"
+  poll: "Peilingen"
+desktop/views/components/taskmanager.vue:
+  title: "Taakbeheer"
+desktop/views/components/timeline.vue:
+  home: "Startpagina"
+  local: "Lokaal"
+  hybrid: "ソーシャル"
+  global: "Algemeen"
+  list: "Lijsten"
+desktop/views/components/ui.header.vue:
+  welcome-back: "おかえりなさい、"
+  adjective: "さん"
+desktop/views/components/ui.header.account.vue:
+  profile: "Je profiel"
+  drive: "Drive"
+  favorites: "Favorieten"
+  lists: "Lijsten"
+  follow-requests: "フォロー申請"
+  customize: "Aanpassen"
+  admin: "管理"
+  settings: "Instellingen"
+  signout: "Uitloggen"
+  dark: "Donkere modus"
+desktop/views/components/ui.header.nav.vue:
+  home: "Startpagina"
+  deck: "デッキ"
+  messaging: "Berichten"
+  game: "Othello spelen"
+desktop/views/components/ui.header.notifications.vue:
+  title: "Meldingen"
+desktop/views/components/ui.header.post.vue:
+  post: "Nieuw bericht opstellen"
+desktop/views/components/ui.header.search.vue:
+  placeholder: "Zoeken"
+desktop/views/components/received-follow-requests-window.vue:
+  title: "フォロー申請"
+  accept: "承認"
+  reject: "拒否"
+desktop/views/components/user-lists-window.vue:
+  title: "リスト"
+  create-list: "Lijst creëren"
+  list-name: "リスト名"
+desktop/views/components/user-preview.vue:
+  notes: "Berichten"
+  following: "Volgend"
+  followers: "Volgers"
+desktop/views/components/users-list.vue:
+  all: "Alle"
+  iknow: "die ik ken"
+  load-more: "Meer"
+  fetching: "Bezig met laden…"
+desktop/views/components/users-list-item.vue:
+  followed: "Volgt jou"
+desktop/views/components/window.vue:
+  popout: "Uitvouwen"
+  close: "Sluiten"
+desktop/views/pages/admin/admin.vue:
+  dashboard: "ダッシュボード"
+  drive: "ドライブ"
+  users: "ユーザー"
+  update: "更新"
+desktop/views/pages/admin/admin.dashboard.vue:
+  dashboard: "ダッシュボード"
+  all-users: "全てのユーザー"
+  original-users: "このインスタンスのユーザー"
+  all-notes: "全ての投稿"
+  original-notes: "このインスタンスの投稿"
+  invite: "招待"
+desktop/views/pages/admin/admin.suspend-user.vue:
+  suspend-user: "ユーザーの凍結"
+  suspend: "凍結"
+  suspended: "凍結しました"
+desktop/views/pages/admin/admin.unsuspend-user.vue:
+  unsuspend-user: "ユーザーの凍結の解除"
+  unsuspend: "凍結の解除"
+  unsuspended: "凍結を解除しました"
+desktop/views/pages/admin/admin.verify-user.vue:
+  verify-user: "ユーザーの公式アカウント設定"
+  verify: "公式アカウントにする"
+  verified: "公式アカウントにしました"
+desktop/views/pages/admin/admin.unverify-user.vue:
+  unverify-user: "ユーザーの公式アカウント解除"
+  unverify: "公式アカウントを解除する"
+  unverified: "公式アカウントを解除しました"
+desktop/views/pages/deck/deck.tl-column.vue:
+  is-media-only: "メディア投稿のみ"
+  is-media-view: "メディアビュー"
+  edit: "オプション"
+desktop/views/pages/deck/deck.note.vue:
+  reposted-by: "{}がRenote"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+desktop/views/pages/stats/stats.vue:
+  all-users: "全てのユーザー"
+  original-users: "このインスタンスのユーザー"
+  all-notes: "全ての投稿"
+  original-notes: "このインスタンスの投稿"
+desktop/views/pages/welcome.vue:
+  about: "詳しく..."
+  gotit: "わかった"
+  signin: "Inloggen"
+  signup: "Registreren"
+  signin-button: "Inloggen"
+  signup-button: "Registreren"
+  timeline: "Tijdlijn"
+  announcements: "お知らせ"
+  photos: "最近の画像"
+  powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
+desktop/views/pages/drive.vue:
+  title: "Misskey Drive"
+desktop/views/pages/favorites.vue:
+  more: "Meer laden"
+desktop/views/pages/home-customize.vue:
+  title: "Startpagina aanpassen"
+desktop/views/pages/note.vue:
+  prev: "Vorige notitie"
+  next: "Volgende notitie"
+desktop/views/pages/selectdrive.vue:
+  title: "Bestand(en) kiezen"
+  ok: "Oké"
+  cancel: "Annuleren"
+  upload: "Bestanden uploaden van je PC"
+desktop/views/pages/search.vue:
+  not-available: "検索機能はインスタンスの設定で無効になっています。"
+  not-found: "「{}」に関する投稿は見つかりませんでした。"
+desktop/views/pages/share.vue:
+  share-with: "{}で共有"
+desktop/views/pages/tag.vue:
+  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
+desktop/views/pages/user-list.users.vue:
+  users: "Gebruiker"
+  add-user: "Gebruiker toevoegen"
+  username: "Gebruikersnaam"
+desktop/views/pages/user/user.followers-you-know.vue:
+  title: "Volgers die je kent"
+  loading: "Bezig met laden"
+  no-users: "Geen gebruikers"
+desktop/views/pages/user/user.friends.vue:
+  title: "Frequent beantwoord"
+  loading: "Bezig met laden"
+  no-users: "Geen gebruikers"
+desktop/views/pages/user/user.vue:
+  is-suspended: "このユーザーは凍結されています。"
+  is-remote: "このユーザーはリモートユーザーです。"
+  view-remote: "正確な情報を見る"
+desktop/views/pages/user/user.home.vue:
+  last-used-at: "Laatst actief: "
+desktop/views/pages/user/user.photos.vue:
+  title: "Foto's"
+  loading: "Bezig met laden"
+  no-photos: "Geen foto's"
+desktop/views/pages/user/user.profile.vue:
+  follows-you: "Volgt jou"
+  stalk: "Stalken"
+  stalking: "Stalkend"
+  unstalk: "Stoppen met stalken"
+  mute: "Dempen"
+  muted: "Dempend"
+  unmute: "Ontdempen"
+  push-to-a-list: "リストに追加"
+  list-pushed: "{user}を{list}に追加しました。"
+desktop/views/pages/user/user.header.vue:
+  posts: "投稿"
+  following: "フォロー"
+  followers: "フォロワー"
+  is-bot: "このアカウントはBotです"
+desktop/views/pages/user/user.timeline.vue:
+  default: "Berichten"
+  with-replies: "Berichten en antwoorden"
+  with-media: "Media"
+  empty: "Deze gebruiker heeft nog niks geplaatst."
+desktop/views/widgets/messaging.vue:
+  title: "Gesprekken"
+desktop/views/widgets/notifications.vue:
+  title: "Meldingen"
+  settings: "Instellingen"
+desktop/views/widgets/polls.vue:
+  title: "Peilingen"
+  refresh: "Anderen tonen"
+  nothing: "Niks"
+desktop/views/widgets/post-form.vue:
+  title: "Bericht"
+  note: "Bericht"
+desktop/views/widgets/profile.vue:
+  update-banner: "Klik om je omslagfoto te wijzigen"
+  update-avatar: "Klik om je gebruikersafbeelding te wijzigen"
+desktop/views/widgets/trends.vue:
+  title: "Populair"
+  refresh: "Anderen tonen"
+  nothing: "Niks"
+desktop/views/widgets/users.vue:
+  title: "Aanbevolen gebruikers"
+  refresh: "Anderen tonen"
+  no-one: "Niemand"
+mobile/views/components/drive.vue:
+  drive: "Drive"
+  used: "gebruikt"
+  folder-count: "Map(pen)"
+  count-separator: ", "
+  file-count: "Bestand(en)"
+  load-more: "Meer laden"
+  nothing-in-drive: "Niks"
+  folder-is-empty: "Deze map is leeg"
+  prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
+  deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
+  folder-name: "フォルダー名"
+  root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
+  root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
+  url-prompt: "アップロードしたいファイルのURL"
+  uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
+mobile/views/components/drive-file-detail.vue:
+  rename: "名前を変更"
+mobile/views/components/drive-file-chooser.vue:
+  select-file: "Kies een bestand"
+mobile/views/components/drive-folder-chooser.vue:
+  select-folder: "Kies een map"
+mobile/views/components/drive.file.vue:
+  nsfw: "閲覧注意"
+mobile/views/components/drive.file-detail.vue:
+  download: "Downloaden"
+  rename: "Naam wijzigen"
+  move: "Verplaatsen"
+  hash: "Hash (md5)"
+  exif: "EXIF"
+  nsfw: "閲覧注意"
+mobile/views/components/media-image.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+mobile/views/components/media-video.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+mobile/views/components/follow-button.vue:
+  following: "フォロー中"
+  follow: "Volgen"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+mobile/views/components/friends-maker.vue:
+  title: "気になるユーザーをフォロー"
+  empty: "おすすめのユーザーは見つかりませんでした。"
+  fetching: "読み込んでいます"
+  refresh: "もっと見る"
+  close: "閉じる"
+mobile/views/components/note.vue:
+  reposted-by: "Renote door {}"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  location: "位置情報"
+mobile/views/components/note-detail.vue:
+  reply: "Beantwoorden"
+  reaction: "Reactie"
+  reposted-by: "{}がRenote"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  location: "位置情報"
+mobile/views/components/note-preview.vue:
+  admin: "admin"
+  bot: "bot"
+  cat: "cat"
+mobile/views/components/note-sub.vue:
+  admin: "admin"
+  bot: "bot"
+  cat: "cat"
+mobile/views/components/notes.vue:
+  failed: "読み込みに失敗しました。"
+  retry: "リトライ"
+mobile/views/components/notifications.vue:
+  more: "Meer"
+  empty: "Geen meldingen"
+mobile/views/components/post-form.vue:
+  add-visible-user: "ユーザーを追加"
+  submit: "Plaatsen"
+  reply: "Antwoord"
+  renote: "Renote"
+  quote-placeholder: "この投稿を引用... (オプション)"
+  reply-placeholder: "Deze notitie beantwoorden..."
+  cw-placeholder: "内容への注釈 (オプション)"
+  location-alert: "お使いの端末は位置情報に対応していません"
+  error: "エラー"
+  username-prompt: "ユーザー名を入力してください"
+mobile/views/components/sub-note-content.vue:
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  media-count: "{} media"
+  poll: "Peiling"
+mobile/views/components/timeline.vue:
+  empty: "Geen notities"
+  load-more: "Meer"
+mobile/views/components/ui.header.vue:
+  welcome-back: "おかえりなさい、"
+  adjective: "さん"
+mobile/views/components/ui.nav.vue:
+  timeline: "タイムライン"
+  notifications: "Meldingen"
+  messaging: "Gesprekken"
+  follow-requests: "フォロー申請"
+  search: "Zoeken"
+  drive: "Drive"
+  favorites: "お気に入り"
+  user-lists: "リスト"
+  widgets: "ウィジェット"
+  game: "ゲーム"
+  darkmode: "ダークモード"
+  settings: "Instellingen"
+  admin: "管理"
+  about: "Over Misskey"
+mobile/views/components/user-timeline.vue:
+  no-notes: "Het lijkt erop dat deze gebruiker nog niks heeft geplaatst"
+  no-notes-with-media: "Er zijn geen notities met bijgevoegde media"
+  load-more: "Meer"
+mobile/views/components/users-list.vue:
+  all: "Alles"
+  known: "die je kent"
+  load-more: "Meer"
+mobile/views/pages/favorites.vue:
+  title: "お気に入り"
+mobile/views/pages/user-lists.vue:
+  title: "リスト"
+  enter-list-name: "リスト名を入力してください"
+mobile/views/pages/drive.vue:
+  drive: "Drive"
+  more: "もっと見る"
+mobile/views/pages/signup.vue:
+  lets-start: "📦 始めましょう"
+mobile/views/pages/followers.vue:
+  followers-of: "Volgers van {}"
+mobile/views/pages/following.vue:
+  following-of: "Volgenden van {}"
+mobile/views/pages/home.vue:
+  home: "ホーム"
+  local: "ローカル"
+  hybrid: "ソーシャル"
+  global: "グローバル"
+mobile/views/pages/tag.vue:
+  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
+mobile/views/pages/welcome.vue:
+  signup: "新規登録"
+mobile/views/pages/widgets.vue:
+  dashboard: "ダッシュボード"
+  widgets-hints: "ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。"
+  add-widget: "追加"
+  customization-tips: "カスタマイズのヒント"
+mobile/views/pages/widgets/activity.vue:
+  activity: "アクティビティ"
+mobile/views/pages/share.vue:
+  share-with: "{}で共有"
+mobile/views/pages/messaging.vue:
+  messaging: "Gesprekken"
+mobile/views/pages/messaging-room.vue:
+  messaging: "Gesprekken"
+mobile/views/pages/received-follow-requests.vue:
+  title: "フォロー申請"
+  accept: "承認"
+  reject: "拒否"
+mobile/views/pages/note.vue:
+  title: "Bericht"
+  prev: "Vorige notitie"
+  next: "Volgende notitie"
+mobile/views/pages/notifications.vue:
+  notifications: "Meldingen"
+  read-all: "Weet je zeker dat je alle meldingen wilt markeren als gelezen?"
+mobile/views/pages/games/reversi.vue:
+  reversi: "リバーシ"
+mobile/views/pages/settings/settings.profile.vue:
+  title: "Profiel"
+  name: "Naam"
+  account: "Account"
+  location: "Locatie"
+  description: "Omschrijving"
+  birthday: "Geboortedatum"
+  avatar: "Gebruikersafbeelding"
+  banner: "Omslagfoto"
+  is-cat: "Dit account is een Kat"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
+  save: "Profiel bijwerken"
+  saved: "Profiel bijgewerkt"
+  uploading: "Bezig met uploaden"
+  upload-failed: "Upload mislukt"
+mobile/views/pages/search.vue:
+  search: "Zoeken"
+  empty: "Geen berichten gevonden voor '{}'"
+  not-found: "「{}」に関する投稿は見つかりませんでした。"
+mobile/views/pages/selectdrive.vue:
+  select-file: "Kies een bestand"
+mobile/views/pages/settings.vue:
+  signed-in-as: "Ingelogd als {}"
+  lang: "Taal"
+  lang-tip: "Je moet de pagina herladen om de wijzigingen toe te passen."
+  recommended: "Aanbevolen"
+  auto: "Automatisch"
+  specify-language: "Taal opgeven"
+  design: "Ontwerp en weergave"
+  dark-mode: "Donkere modus"
+  i-am-under-limited-internet: "Ik heb beperkt internet"
+  circle-icons: "Ronde pictogrammen gebruiken"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
+  timeline: "Tijdlijn"
+  show-reply-target: "Antwoordknop tonen"
+  show-my-renotes: "Mijn renotes tonen"
+  show-renoted-my-notes: "Mijn gerenote notities tonen"
+  show-local-renotes: "ローカルの投稿のRenoteを表示する"
+  post-style: "Berichtontwerp"
+  post-style-standard: "Standaard"
+  post-style-smart: "Slim"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
+  behavior: "Gedrag"
+  fetch-on-scroll: "Ophalen bij scrollen"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
+  disable-via-mobile: "Zonder 'mobiele berichten'"
+  load-raw-images: "添付された画像を高画質で表示する"
+  load-remote-media: "リモートサーバーのメディアを表示する"
+  twitter: "Twitter-integratie"
+  twitter-connect: "Mijn Twitter-account verbinden"
+  twitter-reconnect: "Opnieuw verbinden"
+  twitter-disconnect: "Verbinding verbreken"
+  update: "Misskey-update"
+  version: "Huidige versie:"
+  latest-version: "Nieuwste versie:"
+  update-checking: "Bezig met controleren op updates"
+  check-for-updates: "Controleren op updates"
+  no-updates: "Geen updates beschikbaar"
+  no-updates-desc: "Je Misskey is bijgewerkt."
+  update-available: "Nieuwe versie beschikbaar!"
+  update-available-desc: "Herlaad de pagina om de updates toe te passen."
+  settings: "Instellingen"
+  signout: "Uitloggen"
+  sound: "サウンド"
+  enable-sounds: "サウンドを有効にする"
+mobile/views/pages/user.vue:
+  follows-you: "Volgt jou"
+  following: "Volgend"
+  followers: "Volgers"
+  notes: "Berichten"
+  overview: "Overzicht"
+  timeline: "Tijdlijn"
+  media: "Media"
+  is-suspended: "Dit account is geschorst."
+  is-remote: "Deze gebruiker is een externe gebruiker; de informatie is daarom niet volledig. "
+  view-remote: "Volledige informatie bekijken"
+mobile/views/pages/user/home.vue:
+  recent-notes: "Recente notities"
+  images: "Afbeeldingen"
+  activity: "Activiteit"
+  keywords: "Sleutelwoorden"
+  domains: "Domeinnamen"
+  frequently-replied-users: "Frequent gesproken gebruikers"
+  followers-you-know: "Volgers die je kent"
+  last-used-at: "Laatst actief:"
+mobile/views/pages/user/home.followers-you-know.vue:
+  loading: "Bezig met laden"
+  no-users: "Geen gebruikers"
+mobile/views/pages/user/home.friends.vue:
+  loading: "Bezig met laden"
+  no-users: "Geen gebruikers"
+mobile/views/pages/user/home.notes.vue:
+  loading: "Bezig met laden"
+  no-notes: "Geen notities"
+mobile/views/pages/user/home.photos.vue:
+  loading: "Bezig met laden"
+  no-photos: "Geen foto's"
+docs:
+  edit-this-page-on-github: "Heb je een fout ontdekt of wil je bijdragen aan de documentatie? "
+  edit-this-page-on-github-link: "Bewerk deze pagina op GitHub!"
+  api:
+    entities:
+      properties: "Eigenschappen"
+    endpoints:
+      params: "Parameters"
+      no-params: "パラメータはありません"
+      res: "Antwoord"
+      require-credential: "このエンドポイントは認証情報が必須です。"
+      require-permission: "このエンドポイントは{permission}の権限を必要とします。"
+      has-limit: "レートリミットがあります。"
+      duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
+      min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
+      show-src: "このエンドポイントのソースコードも閲覧できます。"
+      show-src-link: "コードをGitHubで見る"
+      generated: "このドキュメントはAPI定義に基づき自動生成されています。"
+    props:
+      name: "Naam"
+      type: "Type"
+      description: "Omschrijving"
+dev/views/index.vue:
+  manage-apps: "アプリの管理"
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
new file mode 100644
index 0000000000..28db241f86
--- /dev/null
+++ b/locales/no-NO.yml
@@ -0,0 +1,1287 @@
+---
+meta:
+  lang: "norsk"
+  divider: ""
+common:
+  misskey: "A ⭐ of fediverse"
+  about-title: "A ⭐ of fediverse."
+  about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
+  adblock:
+    detected: "広告ブロッカーを無効にしてください"
+    warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
+  application-authorization: "アプリの連携"
+  close: "閉じる"
+  do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
+  got-it: "わかった"
+  customization-tips:
+    title: "カスタマイズのヒント"
+    paragraph1: "ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。"
+    paragraph2: "一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。"
+    paragraph3: "ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。"
+    paragraph4: "カスタマイズを終了するには、右上の「完了」をクリックします。"
+    gotit: "Got it!"
+  notification:
+    file-uploaded: "ファイルがアップロードされました"
+    message-from: "{}さんからメッセージ:"
+    reversi-invited: "対局への招待があります"
+    reversi-invited-by: "{}さんから"
+    notified-by: "{}さんから"
+    reply-from: "{}さんから返信:"
+    quoted-by: "{}さんが引用:"
+  time:
+    unknown: "ukjent"
+    future: "fremtidig"
+    just_now: "akkurat nå"
+    seconds_ago: "{} sekunder siden"
+    minutes_ago: "{} minutter siden"
+    hours_ago: "{} time siden"
+    days_ago: "{} dag siden"
+    weeks_ago: "{} uke(r) siden"
+    months_ago: "{} måned(er) siden"
+    years_ago: "{} år siden"
+  month-and-day: "{month}月 {day}日"
+  trash: "ゴミ箱"
+  weekday-short:
+    sunday: "S"
+    monday: "M"
+    tuesday: "T"
+    wednesday: "O"
+    thursday: "T"
+    friday: "F"
+    saturday: "L"
+  weekday:
+    sunday: "日曜日"
+    monday: "月曜日"
+    tuesday: "火曜日"
+    wednesday: "水曜日"
+    thursday: "木曜日"
+    friday: "金曜日"
+    saturday: "土曜日"
+  reactions:
+    like: "Lik"
+    love: "Elsk"
+    laugh: "Le"
+    hmm: "Hmm…?"
+    surprise: "Wow"
+    congrats: "Gratulerer!"
+    angry: "Sint"
+    confused: "Forvirret"
+    rip: "RIP"
+    pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
+  note-placeholders:
+    a: "今どうしてる?"
+    b: "何かありましたか?"
+    c: "何をお考えですか?"
+    d: "言いたいことは?"
+    e: "ここに書いてください"
+    f: "あなたが書くのを待っています..."
+  search: "検索"
+  delete: "削除"
+  loading: "読み込み中"
+  ok: "わかった"
+  update-available-title: "更新があります"
+  update-available: "En ny versjon av Misskey er nå tilgjengelig ({newer}, nåværende versjon er {current}). Last inn siden igjen for at oppdateringen skal tre i kraft."
+  my-token-regenerated: "Ditt synbol har blitt generert. Du vil nå bli utlogget."
+  i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
+  show-reversi-board-labels: "リバーシのボードの行と列のラベルを表示"
+  use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
+  verified-user: "公式アカウント"
+  disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
+  reversi:
+    drawn: "引き分け"
+    my-turn: "あなたのターンです"
+    opponent-turn: "相手のターンです"
+    turn-of: "{}のターンです"
+    past-turn-of: "{}のターン"
+    won: "{}の勝ち"
+    black: "黒"
+    white: "白"
+    total: "合計"
+    this-turn: "{}ターン目"
+  widgets:
+    analog-clock: "アナログ時計"
+    profile: "プロフィール"
+    calendar: "カレンダー"
+    timemachine: "カレンダー(タイムマシン)"
+    activity: "アクティビティ"
+    rss: "RSSリーダー"
+    memo: "付箋"
+    trends: "トレンド"
+    photo-stream: "フォトストリーム"
+    posts-monitor: "投稿チャート"
+    slideshow: "スライドショー"
+    version: "バージョン"
+    broadcast: "ブロードキャスト"
+    notifications: "通知"
+    users: "おすすめユーザー"
+    polls: "アンケート"
+    post-form: "投稿フォーム"
+    messaging: "メッセージ"
+    server: "サーバー情報"
+    donation: "寄付のお願い"
+    nav: "ナビゲーション"
+    tips: "ヒント"
+    hashtags: "ハッシュタグ"
+  deck:
+    widgets: "ウィジェット"
+    home: "ホーム"
+    local: "ローカル"
+    hybrid: "ソーシャル"
+    global: "グローバル"
+    notifications: "通知"
+    list: "リスト"
+    swap-left: "左に移動"
+    swap-right: "右に移動"
+    swap-up: "上に移動"
+    swap-down: "下に移動"
+    remove: "カラムを削除"
+    add-column: "カラムを追加"
+    rename: "名前を変更"
+    stack-left: "左に重ねる"
+    pop-right: "右に出す"
+auth/views/form.vue:
+  share-access: "<i>{{ app.name }}</i>があなたのアカウントにアクセスすることを<b>許可</b>しますか?"
+  permission-ask: "このアプリは次の権限を要求しています:"
+  account-read: "アカウントの情報を見る。"
+  account-write: "アカウントの情報を操作する。"
+  note-write: "投稿する。"
+  like-write: "いいねしたりいいね解除する。"
+  following-write: "フォローしたりフォロー解除する。"
+  drive-read: "ドライブを見る。"
+  drive-write: "ドライブを操作する。"
+  notification-read: "通知を見る。"
+  notification-write: "通知を操作する。"
+  cancel: "キャンセル"
+  accept: "アクセスを許可"
+auth/views/index.vue:
+  loading: "読み込み中"
+  denied: "アプリケーションの連携をキャンセルしました。"
+  denied-paragraph: "このアプリがあなたのアカウントにアクセスすることはありません。"
+  already-authorized: "このアプリは既に連携済みです"
+  allowed: "アプリケーションの連携を許可しました"
+  callback-url: "アプリケーションに戻っています"
+  please-go-back: "アプリケーションに戻って、やっていってください。"
+  error: "セッションが存在しません。"
+  sign-in: "サインインしてください"
+common/views/components/games/reversi/reversi.vue:
+  matching:
+    waiting-for: "{}を待っています"
+    cancel: "キャンセル"
+common/views/components/games/reversi/reversi.game.vue:
+  surrender: "投了"
+  surrendered: "投了により"
+  is-llotheo: "石の少ない方が勝ち(ロセオ)"
+  looped-map: "ループマップ"
+  can-put-everywhere: "どこでも置けるモード"
+common/views/components/games/reversi/reversi.index.vue:
+  title: "Misskey Reversi"
+  sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
+  invite: "招待"
+  rule: "遊び方"
+  rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
+  mode-invite: "招待"
+  mode-invite-desc: "指定したユーザーと対戦するモードです。"
+  invitations: "対局の招待があります!"
+  my-games: "自分の対局"
+  all-games: "みんなの対局"
+  enter-username: "ユーザー名を入力してください"
+  game-state:
+    ended: "終了"
+    playing: "進行中"
+common/views/components/games/reversi/reversi.room.vue:
+  settings-of-the-game: "ゲームの設定"
+  choose-map: "マップを選択"
+  random: "ランダム"
+  black-or-white: "先手/後手"
+  black-is: "{}が黒"
+  rules: "ルール"
+  is-llotheo: "石の少ない方が勝ち(ロセオ)"
+  looped-map: "ループマップ"
+  can-put-everywhere: "どこでも置けるモード"
+  settings-of-the-bot: "Botの設定"
+  this-game-is-started-soon: "ゲームは数秒後に開始されます"
+  waiting-for-other: "相手の準備が完了するのを待っています"
+  waiting-for-me: "あなたの準備が完了するのを待っています"
+  waiting-for-both: "準備中"
+  cancel: "キャンセル"
+  ready: "準備完了"
+  cancel-ready: "準備続行"
+common/views/components/connect-failed.vue:
+  title: "Kunne ikke koble til tjeneren."
+  description: "Det er enten et problem med internettilknytningen din, eller så har tjeneren blitt tatt ned for vedlikehold. {Prøv igjen} senere."
+  thanks: "いつもMisskeyをご利用いただきありがとうございます。"
+  troubleshoot: "トラブルシュート"
+common/views/components/connect-failed.troubleshooter.vue:
+  title: "トラブルシューティング"
+  network: "ネットワーク接続"
+  checking-network: "ネットワーク接続を確認中"
+  internet: "インターネット接続"
+  checking-internet: "インターネット接続を確認中"
+  server: "サーバー接続"
+  checking-server: "サーバー接続を確認中"
+  finding: "問題を調べています"
+  no-network: "ネットワークに接続されていません"
+  no-network-desc: "お使いのPCのネットワーク接続が正常か確認してください。"
+  no-internet: "インターネットに接続されていません"
+  no-internet-desc: "ネットワークには接続されていますが、インターネットには接続されていないようです。お使いのPCのインターネット接続が正常か確認してください。"
+  no-server: "Misskeyのサーバーに接続できません"
+  no-server-desc: "お使いのPCのインターネット接続は正常ですが、Misskeyのサーバーには接続できませんでした。サーバーがダウンまたはメンテナンスしている可能性があるので、しばらくしてから再度御アクセスください。"
+  success: "Misskeyのサーバーに接続できました"
+  success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
+  flush: "キャッシュの削除"
+  set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
+common/views/components/messaging.vue:
+  search-user: "ユーザーを探す"
+  you: "あなた"
+  no-history: "履歴はありません"
+common/views/components/messaging-room.vue:
+  empty: "このユーザーと話したことはありません"
+  more: "もっと読む"
+  no-history: "これより過去の履歴はありません"
+  resize-form: "ドラッグしてフォームの広さを調整"
+  new-message: "新しいメッセージがあります"
+  only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
+common/views/components/messaging-room.form.vue:
+  input-message-here: "ここにメッセージを入力"
+  send: "送信"
+  attach-from-local: "PCからファイルを添付する"
+  attach-from-drive: "ドライブからファイルを添付する"
+  only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
+common/views/components/messaging-room.message.vue:
+  is-read: "既読"
+  deleted: "このメッセージは削除されました"
+common/views/components/nav.vue:
+  about: "Misskeyについて"
+  stats: "統計"
+  status: "ステータス"
+  wiki: "Wiki"
+  donors: "ドナー"
+  repository: "リポジトリ"
+  develop: "開発者"
+  feedback: "フィードバック"
+common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
+  favorite: "お気に入り"
+  pin: "ピン留め"
+  delete: "削除"
+  delete-confirm: "この投稿を削除しますか?"
+  remote: "投稿元で見る"
+common/views/components/poll.vue:
+  vote-to: "「{}」に投票する"
+  vote-count: "{}票"
+  total-users: "{}人が投票"
+  vote: "投票する"
+  show-result: "結果を見る"
+  voted: "投票済み"
+common/views/components/poll-editor.vue:
+  no-only-one-choice: "アンケートには、選択肢が最低2つ必要です"
+  choice-n: "選択肢{}"
+  remove: "この選択肢を削除"
+  add: "+選択肢を追加"
+  destroy: "アンケートを破棄"
+common/views/components/reaction-picker.vue:
+  choose-reaction: "リアクションを選択"
+common/views/components/signin.vue:
+  username: "ユーザー名"
+  password: "パスワード"
+  token: "トークン"
+  signing-in: "やってます..."
+  signin: "サインイン"
+  or: "または"
+  signin-with-twitter: "Twitterでログイン"
+  login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
+common/views/components/signup.vue:
+  invitation-code: "招待コード"
+  invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
+  username: "ユーザー名"
+  checking: "確認しています..."
+  available: "利用できます"
+  unavailable: "既に利用されています"
+  error: "通信エラー"
+  invalid-format: "a~z、A~Z、0~9、_が使えます"
+  too-short: "1文字以上でお願いします!"
+  too-long: "20文字以内でお願いします"
+  password: "パスワード"
+  password-placeholder: "8文字以上を推奨します"
+  weak-password: "弱いパスワード"
+  normal-password: "まあまあのパスワード"
+  strong-password: "強いパスワード"
+  retype: "再入力"
+  retype-placeholder: "確認のため再入力してください"
+  password-matched: "確認されました"
+  password-not-matched: "一致していません"
+  recaptcha: "認証"
+  create: "アカウント作成"
+  some-error: "何らかの原因によりアカウントの作成に失敗しました。再度お試しください。"
+common/views/components/special-message.vue:
+  new-year: "Happy New Year!"
+  christmas: "Merry Christmas!"
+common/views/components/stream-indicator.vue:
+  connecting: "接続中"
+  reconnecting: "再接続中"
+  connected: "接続完了"
+common/views/components/twitter-setting.vue:
+  description: "お使いのTwitterアカウントをお使いのMisskeyアカウントに接続しておくと、プロフィールでTwitterアカウント情報が表示されるようになったり、Twitterを用いた便利なサインインを利用できるようになります。"
+  connected-to: "次のTwitterアカウントに接続されています"
+  detail: "詳細..."
+  reconnect: "再接続する"
+  connect: "Twitterと接続する"
+  disconnect: "切断する"
+common/views/components/uploader.vue:
+  waiting: "待機中"
+common/views/components/visibility-chooser.vue:
+  public: "公開"
+  home: "ホーム"
+  home-desc: "ホームタイムラインにのみ公開"
+  followers: "フォロワー"
+  followers-desc: "自分のフォロワーにのみ公開"
+  specified: "ダイレクト"
+  specified-desc: "指定したユーザーにのみ公開"
+  private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
+common/views/widgets/broadcast.vue:
+  fetching: "確認中"
+  no-broadcasts: "お知らせはありません"
+  have-a-nice-day: "良い一日を!"
+  next: "次"
+common/views/widgets/calendar.vue:
+  year: "{}年"
+  month: "{}月"
+  day: "{}日"
+  today: "今日:"
+  this-month: "今月:"
+  this-year: "今年:"
+common/views/widgets/donation.vue:
+  title: "寄付のお願い"
+  text: "Misskeyの運営にはドメイン、サーバー等のコストが掛かります。Misskeyは広告を掲載したりしないため、収入を皆様からの寄付に頼っています。もしご興味があれば、{}までご連絡ください。ご協力ありがとうございます。"
+common/views/widgets/photo-stream.vue:
+  title: "フォトストリーム"
+  no-photos: "写真はありません"
+common/views/widgets/posts-monitor.vue:
+  title: "投稿チャート"
+  toggle: "表示を切り替え"
+common/views/widgets/hashtags.vue:
+  title: "ハッシュタグ"
+common/views/widgets/server.vue:
+  title: "サーバー情報"
+  toggle: "表示を切り替え"
+common/views/widgets/memo.vue:
+  title: "付箋"
+  memo: "ここに書いて!"
+  save: "保存"
+common/views/widgets/slideshow.vue:
+  folder-customize-mode: "フォルダを指定するには、カスタマイズモードを終了してください"
+  folder: "クリックしてフォルダを指定してください"
+  no-image: "このフォルダには画像がありません"
+common/views/widgets/tips.vue:
+  tips-line1: "<kbd>t</kbd>でタイムラインにフォーカスできます"
+  tips-line2: "<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます"
+  tips-line3: "投稿フォームにはファイルをドラッグ&ドロップできます"
+  tips-line4: "投稿フォームにクリップボードにある画像データをペーストできます"
+  tips-line5: "ドライブにファイルをドラッグ&ドロップしてアップロードできます"
+  tips-line6: "ドライブでファイルをドラッグしてフォルダ移動できます"
+  tips-line7: "ドライブでフォルダをドラッグしてフォルダ移動できます"
+  tips-line8: "ホームは設定からカスタマイズできます"
+  tips-line9: "MisskeyはAGPLv3です"
+  tips-line10: "タイムマシンウィジェットを利用すると、簡単に過去のタイムラインに遡れます"
+  tips-line11: "投稿の ... をクリックして、投稿をユーザーページにピン留めできます"
+  tips-line13: "投稿に添付したファイルは全てドライブに保存されます"
+  tips-line14: "ホームのカスタマイズ中、ウィジェットを右クリックしてデザインを変更できます"
+  tips-line17: "「**」でテキストを囲むと**強調表示**されます"
+  tips-line19: "いくつかのウィンドウはブラウザの外に切り離すことができます"
+  tips-line20: "カレンダーウィジェットのパーセンテージは、経過の割合を示しています"
+  tips-line21: "APIを利用してbotの開発なども行えます"
+  tips-line23: "まゆかわいいよまゆ"
+  tips-line24: "Misskeyは2014年にサービスを開始しました"
+  tips-line25: "対応ブラウザではMisskeyを開いていなくても通知を受け取れます"
+common/views/pages/follow.vue:
+  signed-in-as: "{}としてサインイン中"
+  following: "フォロー中"
+  follow: "フォロー"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+desktop:
+  banner-crop-title: "バナーとして表示する部分を選択"
+  banner: "バナー"
+  uploading-banner: "新しいバナーをアップロードしています"
+  banner-updated: "バナーを更新しました"
+  choose-banner: "バナーにする画像を選択"
+  avatar-crop-title: "アバターとして表示する部分を選択"
+  avatar: "アバター"
+  uploading-avatar: "新しいアバターをアップロードしています"
+  avatar-updated: "アバターを更新しました"
+  choose-avatar: "アバターにする画像を選択"
+  invalid-filetype: "この形式のファイルはサポートされていません"
+desktop/views/components/activity.chart.vue:
+  total: "Black ... Total"
+  notes: "Blue ... Notes"
+  replies: "Red ... Replies"
+  renotes: "Green ... Renotes"
+desktop/views/components/activity.vue:
+  title: "アクティビティ"
+  toggle: "表示を切り替え"
+desktop/views/components/calendar.vue:
+  title: "{1}年 {2}月"
+  prev: "前の月"
+  next: "次の月"
+  go: "クリックして時間遡行"
+desktop/views/components/charts.vue:
+  title: "チャート"
+  per-day: "1日ごと"
+  per-hour: "1時間ごと"
+  notes: "投稿"
+  users: "ユーザー"
+  drive: "ドライブ"
+  network: "ネットワーク"
+  charts:
+    notes: "投稿の増減 (統合)"
+    local-notes: "投稿の増減 (ローカル)"
+    remote-notes: "投稿の増減 (リモート)"
+    notes-total: "投稿の累計"
+    users: "ユーザーの増減"
+    users-total: "ユーザーの累計"
+    drive: "ドライブ使用量の増減"
+    drive-total: "ドライブ使用量の累計"
+    drive-files: "ドライブのファイル数の増減"
+    drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
+desktop/views/components/choose-file-from-drive-window.vue:
+  choose-file: "ファイル選択中"
+  upload: "PCからドライブにファイルをアップロード"
+  cancel: "キャンセル"
+  ok: "決定"
+  choose-prompt: "ファイルを選択"
+desktop/views/components/choose-folder-from-drive-window.vue:
+  cancel: "キャンセル"
+  ok: "決定"
+  choose-prompt: "フォルダを選択"
+desktop/views/components/crop-window.vue:
+  skip: "クロップをスキップ"
+  cancel: "キャンセル"
+  ok: "決定"
+desktop/views/components/drive-window.vue:
+  used: "使用中"
+  drive: "ドライブ"
+desktop/views/components/drive.file.vue:
+  avatar: "アイコン"
+  banner: "バナー"
+  nsfw: "閲覧注意"
+  contextmenu:
+    rename: "名前を変更"
+    mark-as-sensitive: "閲覧注意に設定"
+    unmark-as-sensitive: "閲覧注意を解除"
+    copy-url: "URLをコピー"
+    download: "ダウンロード"
+    else-files: "その他..."
+    set-as-avatar: "アイコンに設定"
+    set-as-banner: "バナーに設定"
+    open-in-app: "アプリで開く"
+    add-app: "アプリを追加"
+    rename-file: "ファイル名の変更"
+    input-new-file-name: "新しいファイル名を入力してください"
+    copied: "コピー完了"
+    copied-url-to-clipboard: "URLをクリップボードにコピーしました"
+desktop/views/components/drive.folder.vue:
+  unable-to-process: "操作を完了できません"
+  circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
+  unhandled-error: "不明なエラー"
+  contextmenu:
+    move-to-this-folder: "このフォルダへ移動"
+    show-in-new-window: "新しいウィンドウで表示"
+    rename: "名前を変更"
+    rename-folder: "フォルダ名の変更"
+    input-new-folder-name: "新しいフォルダ名を入力してください"
+desktop/views/components/drive.nav-folder.vue:
+  drive: "ドライブ"
+desktop/views/components/drive.vue:
+  search: "検索"
+  load-more: "もっと読み込む"
+  empty-draghover: "ドロップですか?いいですよ、ボクはカワイイですからね"
+  empty-drive: "ドライブには何もありません。"
+  empty-drive-description: "右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。"
+  empty-folder: "このフォルダーは空です"
+  unable-to-process: "操作を完了できません"
+  circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
+  unhandled-error: "不明なエラー"
+  url-upload: "URLアップロード"
+  url-of-file: "アップロードしたいファイルのURL"
+  url-upload-requested: "アップロードをリクエストしました"
+  may-take-time: "アップロードが完了するまで時間がかかる場合があります。"
+  create-folder: "フォルダー作成"
+  folder-name: "フォルダー名"
+  contextmenu:
+    create-folder: "フォルダーを作成"
+    upload: "ファイルをアップロード"
+    url-upload: "URLからアップロード"
+desktop/views/components/media-image.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+desktop/views/components/media-video.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+desktop/views/components/follow-button.vue:
+  following: "フォロー中"
+  follow: "フォロー"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+desktop/views/components/followers-window.vue:
+  followers: "{} のフォロワー"
+desktop/views/components/followers.vue:
+  empty: "フォロワーはいないようです。"
+desktop/views/components/following-window.vue:
+  following: "{} のフォロー"
+desktop/views/components/following.vue:
+  empty: "フォロー中のユーザーはいないようです。"
+desktop/views/components/friends-maker.vue:
+  title: "気になるユーザーをフォロー:"
+  empty: "おすすめのユーザーは見つかりませんでした。"
+  fetching: "読み込んでいます"
+  refresh: "もっと見る"
+  close: "閉じる"
+desktop/views/components/game-window.vue:
+  game: "リバーシ"
+desktop/views/components/home.vue:
+  done: "完了"
+  add-widget: "ウィジェットを追加:"
+  add: "追加"
+desktop/views/input-dialog.vue:
+  cancel: "キャンセル"
+  ok: "決定"
+desktop/views/components/messaging-room-window.vue:
+  title: "メッセージ:"
+desktop/views/components/messaging-window.vue:
+  title: "メッセージ"
+desktop/views/components/note-detail.vue:
+  more: "会話をもっと読み込む"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  reposted-by: "{}がRenote"
+  location: "位置情報"
+  renote: "Renote"
+  add-reaction: "リアクション"
+desktop/views/components/notes.note.vue:
+  reposted-by: "{}がRenote"
+  reply: "返信"
+  renote: "Renote"
+  add-reaction: "リアクション"
+  detail: "詳細"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+desktop/views/components/notes.vue:
+  error: "読み込みに失敗しました。"
+  retry: "リトライ"
+  load-more: "もっと読み込む"
+desktop/views/components/notifications.vue:
+  more: "もっと見る"
+  empty: "ありません!"
+desktop/views/components/post-form.vue:
+  add-visible-user: "+ユーザーを追加"
+  attach-location-information: "位置情報を添付する"
+  hide-contents: "内容を隠す"
+  reply-placeholder: "この投稿への返信..."
+  quote-placeholder: "この投稿を引用..."
+  submit: "投稿"
+  reply: "返信"
+  renote: "Renote"
+  posted: "投稿しました!"
+  replied: "返信しました!"
+  reposted: "Renoteしました!"
+  note-failed: "投稿に失敗しました"
+  reply-failed: "返信に失敗しました"
+  renote-failed: "Renoteに失敗しました"
+  posting: "投稿中"
+  attach-media-from-local: "PCからメディアを添付"
+  attach-media-from-drive: "ドライブからメディアを添付"
+  attach-cancel: "添付取り消し"
+  insert-a-kao: "v('ω')v"
+  create-poll: "アンケートを作成"
+  text-remain: "残り{}文字"
+  recent-tags: "最近"
+  click-to-tagging: "クリックでタグ付け"
+  visibility: "公開範囲"
+  geolocation-alert: "お使いの端末は位置情報に対応していません"
+  error: "エラー"
+  enter-username: "ユーザー名を入力してください"
+  annotations: "内容への注釈 (オプション)"
+desktop/views/components/post-form-window.vue:
+  note: "新規投稿"
+  reply: "返信"
+  attaches: "添付: {}メディア"
+  uploading-media: "{}個のメディアをアップロード中"
+desktop/views/components/progress-dialog.vue:
+  waiting: "待機中"
+desktop/views/components/renote-form.vue:
+  quote: "引用する..."
+  cancel: "キャンセル"
+  renote: "Renote"
+  reposting: "しています..."
+  success: "Renoteしました!"
+  failure: "Renoteに失敗しました"
+desktop/views/components/renote-form-window.vue:
+  title: "この投稿をRenoteしますか?"
+desktop/views/components/settings-window.vue:
+  settings: "設定"
+desktop/views/components/settings.vue:
+  profile: "プロフィール"
+  notification: "通知"
+  apps: "アプリ"
+  mute: "ミュート"
+  drive: "ドライブ"
+  security: "セキュリティ"
+  signin: "サインイン履歴"
+  password: "パスワード"
+  2fa: "二段階認証"
+  other: "その他"
+  license: "ライセンス"
+  behaviour: "動作"
+  fetch-on-scroll: "スクロールで自動読み込み"
+  fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
+  auto-popout: "ウィンドウの自動ポップアウト"
+  auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
+  advanced: "詳細設定"
+  api-via-stream: "ストリームを経由したAPIリクエスト"
+  api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
+  display: "デザインと表示"
+  customize: "ホームをカスタマイズ"
+  choose-wallpaper: "壁紙を選択"
+  delete-wallpaper: "壁紙を削除"
+  dark-mode: "ダークモード"
+  circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
+  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
+  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
+  suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
+  show-clock-on-header: "右上に時計を表示する"
+  show-reply-target: "リプライ先を表示する"
+  show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
+  show-renoted-my-notes: "自分の投稿のRenoteをタイムラインに表示する"
+  show-local-renotes: "ローカルの投稿のRenoteをタイムラインに表示する"
+  show-maps: "マップの自動展開"
+  show-maps-desc: "位置情報が添付された投稿のマップを自動的に展開します。"
+  sound: "サウンド"
+  enable-sounds: "サウンドを有効にする"
+  enable-sounds-desc: "投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。"
+  volume: "ボリューム"
+  test: "テスト"
+  mobile: "モバイル"
+  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
+  language: "言語"
+  pick-language: "言語を選択"
+  recommended: "推奨"
+  auto: "自動"
+  specify-language: "言語を指定"
+  language-desc: "変更はページの再度読み込み後に反映されます。"
+  cache: "キャッシュ"
+  clean-cache: "クリーンアップ"
+  cache-warn: "クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。"
+  cache-cleared: "キャッシュを削除しました"
+  cache-cleared-desc: "ページを再度読み込みしてください。"
+  auto-watch: "投稿の自動ウォッチ"
+  auto-watch-desc: "リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。"
+  about: "Misskeyについて"
+  operator: "このサーバーの運営者"
+  update: "Misskey Update"
+  version: "バージョン:"
+  latest-version: "最新のバージョン:"
+  update-checking: "アップデートを確認中"
+  do-update: "アップデートを確認"
+  update-settings: "詳細設定"
+  prevent-update: "アップデートを延期する(非推奨)"
+  prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。"
+  no-updates: "利用可能な更新はありません"
+  no-updates-desc: "お使いのMisskeyは最新です。"
+  update-available: "新しいバージョンが利用可能です"
+  update-available-desc: "ページを再度読み込みすると更新が適用されます。"
+  advanced-settings: "高度な設定"
+  debug-mode: "デバッグモードを有効にする"
+  debug-mode-desc: "この設定はブラウザに記憶されます。"
+  experimental: "実験的機能を有効にする"
+  experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。"
+  tools: "ツール"
+  task-manager: "タスクマネージャ"
+  third-parties: "サードパーティ"
+desktop/views/components/settings.2fa.vue:
+  intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
+  detail: "詳細..."
+  url: "https://www.google.co.jp/intl/ja/landing/2step/"
+  caution: "登録したデバイスを紛失するなどした場合、Misskeyにサインインできなくなりますのでご注意ください。"
+  register: "デバイスを登録する"
+  already-registered: "既に設定は完了しています。"
+  unregister: "設定を解除"
+  unregistered: "二段階認証が無効になりました。"
+  enter-password: "パスワードを入力してください"
+  authenticator: "まず、Google Authenticatorをお使いのデバイスにインストールします:"
+  howtoinstall: "インストール方法はこちら"
+  scan: "次に、表示されているQRコードをスキャンします:"
+  done: "お使いのデバイスに表示されているトークンを入力して完了します:"
+  submit: "完了"
+  success: "設定が完了しました!"
+  failed: "設定に失敗しました。トークンに誤りがないかご確認ください。"
+  info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。"
+desktop/views/components/settings.api.vue:
+  intro: "APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。"
+  caution: "アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。"
+  regeneration-of-token: "万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。"
+  regenerate-token: "トークンを再生成"
+  token: "Token:"
+  enter-password: "パスワードを入力してください"
+desktop/views/components/settings.apps.vue:
+  no-apps: "連携しているアプリケーションはありません"
+desktop/views/components/settings.drive.vue:
+  max: "中"
+  in-use: "使用中"
+desktop/views/components/settings.mute.vue:
+  no-users: "ミュートしているユーザーはいません"
+desktop/views/components/settings.password.vue:
+  reset: "パスワードを変更する"
+  enter-current-password: "現在のパスワードを入力してください"
+  enter-new-password: "新しいパスワードを入力してください"
+  enter-new-password-again: "もう一度新しいパスワードを入力してください"
+  not-match: "新しいパスワードが一致しません"
+  changed: "パスワードを変更しました"
+desktop/views/components/settings.profile.vue:
+  avatar: "アイコン"
+  choice-avatar: "画像を選択"
+  name: "名前"
+  location: "場所"
+  description: "自己紹介"
+  birthday: "誕生日"
+  save: "保存"
+  locked-account: "アカウントの保護"
+  is-locked: "フォローを承認制にする"
+  other: "その他"
+  is-bot: "このアカウントはBotです"
+  is-cat: "このアカウントはCatです"
+  profile-updated: "プロフィールを更新しました"
+desktop/views/components/sub-note-content.vue:
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  media-count: "{}つのメディア"
+  poll: "アンケート"
+desktop/views/components/taskmanager.vue:
+  title: "タスクマネージャ"
+desktop/views/components/timeline.vue:
+  home: "ホーム"
+  local: "ローカル"
+  hybrid: "ソーシャル"
+  global: "グローバル"
+  list: "リスト"
+desktop/views/components/ui.header.vue:
+  welcome-back: "おかえりなさい、"
+  adjective: "さん"
+desktop/views/components/ui.header.account.vue:
+  profile: "プロフィール"
+  drive: "ドライブ"
+  favorites: "お気に入り"
+  lists: "リスト"
+  follow-requests: "フォロー申請"
+  customize: "ホームのカスタマイズ"
+  admin: "管理"
+  settings: "設定"
+  signout: "サインアウト"
+  dark: "闇に飲まれる"
+desktop/views/components/ui.header.nav.vue:
+  home: "ホーム"
+  deck: "デッキ"
+  messaging: "メッセージ"
+  game: "ゲーム"
+desktop/views/components/ui.header.notifications.vue:
+  title: "通知"
+desktop/views/components/ui.header.post.vue:
+  post: "新規投稿"
+desktop/views/components/ui.header.search.vue:
+  placeholder: "検索"
+desktop/views/components/received-follow-requests-window.vue:
+  title: "フォロー申請"
+  accept: "承認"
+  reject: "拒否"
+desktop/views/components/user-lists-window.vue:
+  title: "リスト"
+  create-list: "リストを作成"
+  list-name: "リスト名"
+desktop/views/components/user-preview.vue:
+  notes: "投稿"
+  following: "フォロー"
+  followers: "フォロワー"
+desktop/views/components/users-list.vue:
+  all: "すべて"
+  iknow: "知り合い"
+  load-more: "もっと"
+  fetching: "読み込んでいます"
+desktop/views/components/users-list-item.vue:
+  followed: "フォローされています"
+desktop/views/components/window.vue:
+  popout: "ポップアウト"
+  close: "閉じる"
+desktop/views/pages/admin/admin.vue:
+  dashboard: "ダッシュボード"
+  drive: "ドライブ"
+  users: "ユーザー"
+  update: "更新"
+desktop/views/pages/admin/admin.dashboard.vue:
+  dashboard: "ダッシュボード"
+  all-users: "全てのユーザー"
+  original-users: "このインスタンスのユーザー"
+  all-notes: "全ての投稿"
+  original-notes: "このインスタンスの投稿"
+  invite: "招待"
+desktop/views/pages/admin/admin.suspend-user.vue:
+  suspend-user: "ユーザーの凍結"
+  suspend: "凍結"
+  suspended: "凍結しました"
+desktop/views/pages/admin/admin.unsuspend-user.vue:
+  unsuspend-user: "ユーザーの凍結の解除"
+  unsuspend: "凍結の解除"
+  unsuspended: "凍結を解除しました"
+desktop/views/pages/admin/admin.verify-user.vue:
+  verify-user: "ユーザーの公式アカウント設定"
+  verify: "公式アカウントにする"
+  verified: "公式アカウントにしました"
+desktop/views/pages/admin/admin.unverify-user.vue:
+  unverify-user: "ユーザーの公式アカウント解除"
+  unverify: "公式アカウントを解除する"
+  unverified: "公式アカウントを解除しました"
+desktop/views/pages/deck/deck.tl-column.vue:
+  is-media-only: "メディア投稿のみ"
+  is-media-view: "メディアビュー"
+  edit: "オプション"
+desktop/views/pages/deck/deck.note.vue:
+  reposted-by: "{}がRenote"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+desktop/views/pages/stats/stats.vue:
+  all-users: "全てのユーザー"
+  original-users: "このインスタンスのユーザー"
+  all-notes: "全ての投稿"
+  original-notes: "このインスタンスの投稿"
+desktop/views/pages/welcome.vue:
+  about: "詳しく..."
+  gotit: "わかった"
+  signin: "ログイン"
+  signup: "新規登録"
+  signin-button: "やってる"
+  signup-button: "やる"
+  timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
+  powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
+desktop/views/pages/drive.vue:
+  title: "Misskey Drive"
+desktop/views/pages/favorites.vue:
+  more: "さらに読み込む"
+desktop/views/pages/home-customize.vue:
+  title: "ホームのカスタマイズ"
+desktop/views/pages/note.vue:
+  prev: "前の投稿"
+  next: "次の投稿"
+desktop/views/pages/selectdrive.vue:
+  title: "ファイルを選択してください"
+  ok: "決定"
+  cancel: "キャンセル"
+  upload: "PCからドライブにファイルをアップロード"
+desktop/views/pages/search.vue:
+  not-available: "検索機能はインスタンスの設定で無効になっています。"
+  not-found: "「{}」に関する投稿は見つかりませんでした。"
+desktop/views/pages/share.vue:
+  share-with: "{}で共有"
+desktop/views/pages/tag.vue:
+  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
+desktop/views/pages/user-list.users.vue:
+  users: "ユーザー"
+  add-user: "ユーザーを追加"
+  username: "ユーザー名"
+desktop/views/pages/user/user.followers-you-know.vue:
+  title: "知り合いのフォロワー"
+  loading: "読み込み中"
+  no-users: "知り合いのフォロワーはいません"
+desktop/views/pages/user/user.friends.vue:
+  title: "よく話すユーザー"
+  loading: "読み込み中"
+  no-users: "よく話すユーザーはいません"
+desktop/views/pages/user/user.vue:
+  is-suspended: "このユーザーは凍結されています。"
+  is-remote: "このユーザーはリモートユーザーです。"
+  view-remote: "正確な情報を見る"
+desktop/views/pages/user/user.home.vue:
+  last-used-at: "最終アクセス"
+desktop/views/pages/user/user.photos.vue:
+  title: "フォト"
+  loading: "読み込み中"
+  no-photos: "写真はありません"
+desktop/views/pages/user/user.profile.vue:
+  follows-you: "フォローされています"
+  stalk: "ストークする"
+  stalking: "ストーキングしています"
+  unstalk: "ストーク解除"
+  mute: "ミュートする"
+  muted: "ミュートしています"
+  unmute: "ミュート解除"
+  push-to-a-list: "リストに追加"
+  list-pushed: "{user}を{list}に追加しました。"
+desktop/views/pages/user/user.header.vue:
+  posts: "投稿"
+  following: "フォロー"
+  followers: "フォロワー"
+  is-bot: "このアカウントはBotです"
+desktop/views/pages/user/user.timeline.vue:
+  default: "投稿"
+  with-replies: "投稿と返信"
+  with-media: "メディア"
+  empty: "このユーザーはまだ何も投稿していないようです。"
+desktop/views/widgets/messaging.vue:
+  title: "メッセージ"
+desktop/views/widgets/notifications.vue:
+  title: "通知"
+  settings: "通知の設定"
+desktop/views/widgets/polls.vue:
+  title: "アンケート"
+  refresh: "他を見る"
+  nothing: "ありません!"
+desktop/views/widgets/post-form.vue:
+  title: "投稿"
+  note: "投稿"
+desktop/views/widgets/profile.vue:
+  update-banner: "クリックでバナー編集"
+  update-avatar: "クリックでアバター編集"
+desktop/views/widgets/trends.vue:
+  title: "トレンド"
+  refresh: "他を見る"
+  nothing: "ありません!"
+desktop/views/widgets/users.vue:
+  title: "おすすめユーザー"
+  refresh: "他を見る"
+  no-one: "いません!"
+mobile/views/components/drive.vue:
+  drive: "ドライブ"
+  used: "使用中"
+  folder-count: "フォルダ"
+  count-separator: "、"
+  file-count: "ファイル"
+  load-more: "もっと読み込む"
+  nothing-in-drive: "ドライブには何もありません"
+  folder-is-empty: "このフォルダは空です"
+  prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
+  deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
+  folder-name: "フォルダー名"
+  root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
+  root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
+  url-prompt: "アップロードしたいファイルのURL"
+  uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
+mobile/views/components/drive-file-detail.vue:
+  rename: "名前を変更"
+mobile/views/components/drive-file-chooser.vue:
+  select-file: "ファイルを選択"
+mobile/views/components/drive-folder-chooser.vue:
+  select-folder: "フォルダーを選択"
+mobile/views/components/drive.file.vue:
+  nsfw: "閲覧注意"
+mobile/views/components/drive.file-detail.vue:
+  download: "ダウンロード"
+  rename: "名前を変更"
+  move: "移動"
+  hash: "ハッシュ (md5)"
+  exif: "EXIF"
+  nsfw: "閲覧注意"
+mobile/views/components/media-image.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+mobile/views/components/media-video.vue:
+  sensitive: "閲覧注意"
+  click-to-show: "クリックして表示"
+mobile/views/components/follow-button.vue:
+  following: "フォロー中"
+  follow: "フォロー"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+mobile/views/components/friends-maker.vue:
+  title: "気になるユーザーをフォロー"
+  empty: "おすすめのユーザーは見つかりませんでした。"
+  fetching: "読み込んでいます"
+  refresh: "もっと見る"
+  close: "閉じる"
+mobile/views/components/note.vue:
+  reposted-by: "{}がRenote"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  location: "位置情報"
+mobile/views/components/note-detail.vue:
+  reply: "返信"
+  reaction: "リアクション"
+  reposted-by: "{}がRenote"
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  location: "位置情報"
+mobile/views/components/note-preview.vue:
+  admin: "admin"
+  bot: "bot"
+  cat: "cat"
+mobile/views/components/note-sub.vue:
+  admin: "admin"
+  bot: "bot"
+  cat: "cat"
+mobile/views/components/notes.vue:
+  failed: "読み込みに失敗しました。"
+  retry: "リトライ"
+mobile/views/components/notifications.vue:
+  more: "もっと見る"
+  empty: "ありません!"
+mobile/views/components/post-form.vue:
+  add-visible-user: "ユーザーを追加"
+  submit: "投稿"
+  reply: "返信"
+  renote: "Renote"
+  quote-placeholder: "この投稿を引用... (オプション)"
+  reply-placeholder: "この投稿への返信..."
+  cw-placeholder: "内容への注釈 (オプション)"
+  location-alert: "お使いの端末は位置情報に対応していません"
+  error: "エラー"
+  username-prompt: "ユーザー名を入力してください"
+mobile/views/components/sub-note-content.vue:
+  private: "この投稿は非公開です"
+  deleted: "この投稿は削除されました"
+  media-count: "{}つのメディア"
+  poll: "アンケート"
+mobile/views/components/timeline.vue:
+  empty: "投稿がありません"
+  load-more: "もっと"
+mobile/views/components/ui.header.vue:
+  welcome-back: "おかえりなさい、"
+  adjective: "さん"
+mobile/views/components/ui.nav.vue:
+  timeline: "タイムライン"
+  notifications: "通知"
+  messaging: "メッセージ"
+  follow-requests: "フォロー申請"
+  search: "検索"
+  drive: "ドライブ"
+  favorites: "お気に入り"
+  user-lists: "リスト"
+  widgets: "ウィジェット"
+  game: "ゲーム"
+  darkmode: "ダークモード"
+  settings: "設定"
+  admin: "管理"
+  about: "Misskeyについて"
+mobile/views/components/user-timeline.vue:
+  no-notes: "このユーザーは投稿していないようです。"
+  no-notes-with-media: "メディア付き投稿はありません。"
+  load-more: "もっと"
+mobile/views/components/users-list.vue:
+  all: "すべて"
+  known: "知り合い"
+  load-more: "もっと"
+mobile/views/pages/favorites.vue:
+  title: "お気に入り"
+mobile/views/pages/user-lists.vue:
+  title: "リスト"
+  enter-list-name: "リスト名を入力してください"
+mobile/views/pages/drive.vue:
+  drive: "ドライブ"
+  more: "もっと見る"
+mobile/views/pages/signup.vue:
+  lets-start: "📦 始めましょう"
+mobile/views/pages/followers.vue:
+  followers-of: "{}のフォロワー"
+mobile/views/pages/following.vue:
+  following-of: "{}のフォロー"
+mobile/views/pages/home.vue:
+  home: "ホーム"
+  local: "ローカル"
+  hybrid: "ソーシャル"
+  global: "グローバル"
+mobile/views/pages/tag.vue:
+  no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
+mobile/views/pages/welcome.vue:
+  signup: "新規登録"
+mobile/views/pages/widgets.vue:
+  dashboard: "ダッシュボード"
+  widgets-hints: "ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。"
+  add-widget: "追加"
+  customization-tips: "カスタマイズのヒント"
+mobile/views/pages/widgets/activity.vue:
+  activity: "アクティビティ"
+mobile/views/pages/share.vue:
+  share-with: "{}で共有"
+mobile/views/pages/messaging.vue:
+  messaging: "メッセージ"
+mobile/views/pages/messaging-room.vue:
+  messaging: "メッセージ"
+mobile/views/pages/received-follow-requests.vue:
+  title: "フォロー申請"
+  accept: "承認"
+  reject: "拒否"
+mobile/views/pages/note.vue:
+  title: "投稿"
+  prev: "前の投稿"
+  next: "次の投稿"
+mobile/views/pages/notifications.vue:
+  notifications: "通知"
+  read-all: "すべての通知を既読にしますか?"
+mobile/views/pages/games/reversi.vue:
+  reversi: "リバーシ"
+mobile/views/pages/settings/settings.profile.vue:
+  title: "プロフィール"
+  name: "名前"
+  account: "アカウント"
+  location: "場所"
+  description: "自己紹介"
+  birthday: "誕生日"
+  avatar: "アイコン"
+  banner: "バナー"
+  is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
+  save: "保存"
+  saved: "プロフィールを保存しました"
+  uploading: "アップロード中"
+  upload-failed: "アップロードに失敗しました"
+mobile/views/pages/search.vue:
+  search: "検索"
+  empty: "「{}」に関する投稿は見つかりませんでした。"
+  not-found: "「{}」に関する投稿は見つかりませんでした。"
+mobile/views/pages/selectdrive.vue:
+  select-file: "ファイルを選択"
+mobile/views/pages/settings.vue:
+  signed-in-as: "{}としてサインイン中"
+  lang: "言語"
+  lang-tip: "変更はページの再読み込み後に反映されます。"
+  recommended: "推奨"
+  auto: "自動"
+  specify-language: "言語を指定"
+  design: "デザインと表示"
+  dark-mode: "ダークモード"
+  i-am-under-limited-internet: "私は通信を制限されている"
+  circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
+  timeline: "タイムライン"
+  show-reply-target: "リプライ先を表示する"
+  show-my-renotes: "自分の行ったRenoteを表示する"
+  show-renoted-my-notes: "自分の投稿のRenoteを表示する"
+  show-local-renotes: "ローカルの投稿のRenoteを表示する"
+  post-style: "投稿の表示スタイル"
+  post-style-standard: "標準"
+  post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
+  behavior: "動作"
+  fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
+  disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
+  load-raw-images: "添付された画像を高画質で表示する"
+  load-remote-media: "リモートサーバーのメディアを表示する"
+  twitter: "Twitter連携"
+  twitter-connect: "Twitterアカウントに接続する"
+  twitter-reconnect: "再接続する"
+  twitter-disconnect: "切断する"
+  update: "Misskey Update"
+  version: "バージョン:"
+  latest-version: "最新のバージョン:"
+  update-checking: "アップデートを確認中"
+  check-for-updates: "アップデートを確認"
+  no-updates: "利用可能な更新はありません"
+  no-updates-desc: "お使いのMisskeyは最新です。"
+  update-available: "新しいバージョンが利用可能です"
+  update-available-desc: "ページを再度読み込みすると更新が適用されます。"
+  settings: "設定"
+  signout: "サインアウト"
+  sound: "サウンド"
+  enable-sounds: "サウンドを有効にする"
+mobile/views/pages/user.vue:
+  follows-you: "フォローされています"
+  following: "フォロー"
+  followers: "フォロワー"
+  notes: "投稿"
+  overview: "概要"
+  timeline: "タイムライン"
+  media: "メディア"
+  is-suspended: "このユーザーは凍結されています。"
+  is-remote: "このユーザーはリモートユーザーです。"
+  view-remote: "正確な情報を見る"
+mobile/views/pages/user/home.vue:
+  recent-notes: "最近の投稿"
+  images: "画像"
+  activity: "アクティビティ"
+  keywords: "キーワード"
+  domains: "頻出ドメイン"
+  frequently-replied-users: "よく会話するユーザー"
+  followers-you-know: "知り合いのフォロワー"
+  last-used-at: "最終ログイン"
+mobile/views/pages/user/home.followers-you-know.vue:
+  loading: "読み込み中"
+  no-users: "知り合いのユーザーはいません"
+mobile/views/pages/user/home.friends.vue:
+  loading: "読み込み中"
+  no-users: "よく会話するユーザーはいません"
+mobile/views/pages/user/home.notes.vue:
+  loading: "読み込み中"
+  no-notes: "投稿はありません"
+mobile/views/pages/user/home.photos.vue:
+  loading: "読み込み中"
+  no-photos: "写真はありません"
+docs:
+  edit-this-page-on-github: "間違いや改善点を見つけましたか?"
+  edit-this-page-on-github-link: "このページをGitHubで編集"
+  api:
+    entities:
+      properties: "プロパティ"
+    endpoints:
+      params: "パラメータ"
+      no-params: "パラメータはありません"
+      res: "レスポンス"
+      require-credential: "このエンドポイントは認証情報が必須です。"
+      require-permission: "このエンドポイントは{permission}の権限を必要とします。"
+      has-limit: "レートリミットがあります。"
+      duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
+      min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
+      show-src: "このエンドポイントのソースコードも閲覧できます。"
+      show-src-link: "コードをGitHubで見る"
+      generated: "このドキュメントはAPI定義に基づき自動生成されています。"
+    props:
+      name: "名前"
+      type: "型"
+      description: "説明"
+dev/views/index.vue:
+  manage-apps: "アプリの管理"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 49257e13e6..2d2d1c1226 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -6,6 +6,19 @@ common:
   misskey: "⭐ Fediwersum"
   about-title: "⭐ Fediwersum"
   about: "Dziękujemy za znalezienie Misskey. Misskey jest <b>zdecentralizowaną platformą mikroblogową</b> powstałą na Ziemi. Ponieważ działa ona w Fediwersum (uniwersum, w którego skład wchodzi wiele sieci społecznościowych), jest ona połączona z innymi platformami społecznościowymi. Spróbujesz odpocząć od zatłoczoneo miasta i zanurzyć się w nowym Internecie?"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "Spróbuj wyłączyć blokadę reklam."
     warning: "<strong>Misskey nie zawiera reklam</strong>, ale część funkcji może nie działać prawidłowo z włączonym blokowaniem reklam."
@@ -68,6 +81,15 @@ common:
     confused: "Zmieszany"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "Co robisz?"
     b: "Co się wydarzyło?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "Wyłącz animowany tekst we wpisach"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "Remis"
     my-turn: "Twoja kolej"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "Wygląda na to, że udało się połączyć. Odśwież stronę."
   flush: "Wyczyść pamięć podręczną"
   set-version: "Określ wersję"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "Znajdź użytkownika"
   you: "Ty"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "Autorzy"
   feedback: "Podziel się opinią"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "Dodaj do ulubionych"
   pin: "Przypnij do profilu"
   delete: "Usuń"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "Bezpośredni"
   specified-desc: "Tylko dla określonych użytkowników"
   private: "Prywatny"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "Sprawdzanie"
   no-broadcasts: "Brak transmisji"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "Przełącz widok"
 common/views/widgets/hashtags.vue:
   title: "Hashtagi"
-  count: "Wspomniany przez {} użytkowników"
-  empty: "Brak popularnych hashtagów"
 common/views/widgets/server.vue:
   title: "Informacje o serwerze"
   toggle: "Przełącz widok"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "Wybierz plik"
   upload: "Wyślij pliki z Twojego komputera"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "Pokaż szczegóły"
   private: "ten wpis jest prywatny"
   deleted: "ten wpis został usunięty"
-  hide: "Zwiń"
-  see-more: "Więcej"
 desktop/views/components/notes.vue:
   error: "Ładowanie nie powiodło się."
   retry: "Spróbuj ponownie"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "Zachowanie"
   fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
   fetch-on-scroll-desc: "Po przewinięciu na dół strony automatycznie zostaną załadowane nowe treści."
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "Automatycznie pojawiające się okna"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "Ustawienia zaawansowane"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "Usuń tło"
   dark-mode: "Tryb ciemny"
   circle-icons: "Używaj okrągłych ikon"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "Używaj gradientów na pasku tytułu okna"
   post-form-on-timeline: "Wyświetlaj formularz tworzenia wpisu w górnej części osi czasu"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "Data urodzenia"
   save: "Aktualizuj profil"
   locked-account: "Zabezpiecz swoje konto"
-  is-locked: "Uczyń wpis prywatnym"
+  is-locked: "フォローを承認制にする"
   other: "Inne"
   is-bot: "To konto jest prowadzone przez bota"
   is-cat: "To konto jest prowadzone przez kota"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "Zaloguj się"
   signup-button: "Zarejestruj się"
   timeline: "Oś czasu"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Oparto o <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Dysk Misskey"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "Zamknij"
 mobile/views/components/note.vue:
   reposted-by: "Udostępniono przez {}"
-  more: "Rozwiń"
-  less: "Zwiń"
   private: "ten wpis jest prywatny"
   deleted: "ten wpis został usunięty"
   location: "Informacje o lokalizacji"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "Awatar"
   banner: "Baner"
   is-cat: "To konto jest prowadzone przez kota"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "Aktualizuj profil"
   saved: "Pomyślnie zaktualizowano profil"
   uploading: "Wysyłanie"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "Tryb ciemny"
   i-am-under-limited-internet: "Ograniczaj zużycie transferu"
   circle-icons: "Używaj okrągłych ikon"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "Oś czasu"
   show-reply-target: "Pokazuj cel odpowiedzi"
   show-my-renotes: "Pokazuj moje udostępnienia"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "Styl wpisów"
   post-style-standard: "Standardowy"
   post-style-smart: "Inteligentny"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "Zachowanie"
   fetch-on-scroll: "Automatycznie ładuj po przeciągnięciu w dół"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "Nie oznaczaj wpisów jako „wysłane z telefonu”"
   load-raw-images: "Wyświetlaj załączone zdjęcia w wysokiej jakości"
   load-remote-media: "Wyświetlaj zawartość multimedialną ze zdalnych serwerów"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "Ustawienia"
   signout: "Wyloguj"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "Śledzi Cię"
   following: "Śledzeni"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 8ab51f21d1..6cc42a4473 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -6,6 +6,19 @@ common:
   misskey: "Uma ⭐ do fediverso"
   about-title: "Uma ⭐ do fediverso."
   about: "Obrigado por encontrar Misskey. Uma <b>plataforma descentralizada de microblog</b> nascida na Terra. Já que ela existe no Fediverso (um universo onde várias plataformas de mídia social são organizadas), ela é ligada com outras plataformas.Por que você não tira uma folga do agito e confusão da cidade, e mergulha em uma nova internet?"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "Por favor, desative o bloqueador de anúncios."
     warning: "Alguns recursos podem não estar disponíveis ou apresentar mal funcionamento se o bloqueio de anúncios estiver ativado. <strong>Misskey não está usando anúncios</strong>"
@@ -68,6 +81,15 @@ common:
     confused: "Confuso"
     rip: "RIP"
     pudding: "Pudim"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "O que está fazendo?"
     b: "O que está acontecendo?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "Conta verificada"
   disable-animated-mfm: "Desativar texto animado nas publicações"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "Empatado"
     my-turn: "Seu turno"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
   flush: "Limpar o cache"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "Você"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "削除"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
   retry: "リトライ"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "Timeline"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Desenvolvido por <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Drive Misskey"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 8520ef23c1..4fe3e3842a 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてください"
     warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
@@ -68,6 +81,15 @@ common:
     confused: "こまこまのこまり"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "今どうしてる?"
     b: "何かありましたか?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "引き分け"
     my-turn: "あなたのターンです"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
   flush: "キャッシュの削除"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "あなた"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "削除"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
   retry: "リトライ"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index c297140adc..167fa0475b 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -6,6 +6,19 @@ common:
   misskey: "A ⭐ of fediverse"
   about-title: "A ⭐ of fediverse."
   about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
+  intro:
+    title: "Misskeyって?"
+    about: "Misskeyはオープンソースの<b>分散型マイクロブログSNS</b>です。リッチで高度にカスタマイズできるUI、投稿へのリアクション、ファイルを一元管理できるドライブなど、先進的な機能を揃えています。また、Fediverseと呼ばれるネットワークに接続できるため、他のSNSともやり取りできます。例えば、あなたが何か投稿すると、その投稿はMisskeyだけでなく他のSNSにも伝わります。ちょうどある惑星から他の惑星に電波を発信している様子をイメージしてください。"
+    features: "特徴"
+    rich-contents: "投稿"
+    rich-contents-desc: "自分の考え、話題の出来事、皆と共有したいことについて発信してください。必要であれば、様々な構文を使って投稿を装飾したり、好きな画像、動画などのファイルやアンケートを添付することもできます。"
+    reaction: "リアクション"
+    reaction-desc: "あなたの気持ちを伝える最も簡単な方法です。Misskeyは、他のユーザーの投稿に様々なリアクションを付けることができます。いちどMisskeyのリアクション機能を体験してしまうと、もう「いいね」の概念しか存在しないSNSには戻れなくなるかもしれません。"
+    ui: "インターフェース"
+    ui-desc: "どのようなUIが使いやすいかは人それぞれです。だから、Misskeyは自由度の高いUIを持っています。レイアウトやデザインを調整したり、カスタマイズ可能な様々なウィジェットを配置したりして、自分だけのホームを作ってください。"
+    drive: "ドライブ"
+    drive-desc: "以前投稿したことのある画像をまた投稿したくなったことはありませんか?もしくは、アップロードしたファイルをフォルダ分けして整理したくなったことはありませんか?Misskeyの根幹に組み込まれたドライブ機能によってそれらが解決します。ファイルの共有も簡単です。"
+    outro: "他にもMisskeyにしかない機能はまだまだあるので、ぜひあなた自身の目で確かめてください。Misskeyは分散型SNSなので、このインスタンスが気に入らなければ他のインスタンスを試すこともできます。それでは、GLHF!"
   adblock:
     detected: "広告ブロッカーを無効にしてください"
     warning: "<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。"
@@ -68,6 +81,15 @@ common:
     confused: "こまこまのこまり"
     rip: "RIP"
     pudding: "Pudding"
+  note-visibility:
+    public: "公開"
+    home: "ホーム"
+    home-desc: "ホームタイムラインにのみ公開"
+    followers: "フォロワー"
+    followers-desc: "自分のフォロワーにのみ公開"
+    specified: "ダイレクト"
+    specified-desc: "指定したユーザーにのみ公開"
+    private: "非公開"
   note-placeholders:
     a: "今どうしてる?"
     b: "何かありましたか?"
@@ -87,6 +109,11 @@ common:
   use-contrast-reversi-stones: "リバーシのアイコンにコントラストを付ける"
   verified-user: "公式アカウント"
   disable-animated-mfm: "投稿内の動きのあるテキストを無効にする"
+  always-show-nsfw: "常に閲覧注意のメディアを表示する"
+  always-mark-nsfw: "常にメディアを閲覧注意として投稿"
+  show-full-acct: "ユーザー名のホストを省略しない"
+  this-setting-is-this-device-only: "このデバイスのみ"
+  do-not-use-in-production: 'これは開発ビルドです。本番環境で使用しないでください。'
   reversi:
     drawn: "引き分け"
     my-turn: "あなたのターンです"
@@ -230,6 +257,9 @@ common/views/components/connect-failed.troubleshooter.vue:
   success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
   flush: "キャッシュの削除"
   set-version: "バージョン指定"
+common/views/components/cw-button.vue:
+  hide: "隠す"
+  show: "もっと見る"
 common/views/components/messaging.vue:
   search-user: "ユーザーを探す"
   you: "あなた"
@@ -260,6 +290,8 @@ common/views/components/nav.vue:
   develop: "開発者"
   feedback: "フィードバック"
 common/views/components/note-menu.vue:
+  detail: "詳細"
+  copy-link: "リンクをコピー"
   favorite: "お気に入り"
   pin: "ピン留め"
   delete: "削除"
@@ -337,6 +369,9 @@ common/views/components/visibility-chooser.vue:
   specified: "ダイレクト"
   specified-desc: "指定したユーザーにのみ公開"
   private: "非公開"
+common/views/components/trends.vue:
+  count: "{}人が投稿"
+  empty: "トレンドなし"
 common/views/widgets/broadcast.vue:
   fetching: "確認中"
   no-broadcasts: "お知らせはありません"
@@ -360,8 +395,6 @@ common/views/widgets/posts-monitor.vue:
   toggle: "表示を切り替え"
 common/views/widgets/hashtags.vue:
   title: "ハッシュタグ"
-  count: "{}人が投稿"
-  empty: "トレンドなし"
 common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
@@ -432,6 +465,7 @@ desktop/views/components/charts.vue:
   notes: "投稿"
   users: "ユーザー"
   drive: "ドライブ"
+  network: "ネットワーク"
   charts:
     notes: "投稿の増減 (統合)"
     local-notes: "投稿の増減 (ローカル)"
@@ -443,6 +477,9 @@ desktop/views/components/charts.vue:
     drive-total: "ドライブ使用量の累計"
     drive-files: "ドライブのファイル数の増減"
     drive-files-total: "ドライブのファイル数の累計"
+    network-requests: "リクエスト"
+    network-time: "応答時間"
+    network-usage: "通信量"
 desktop/views/components/choose-file-from-drive-window.vue:
   choose-file: "ファイル選択中"
   upload: "PCからドライブにファイルをアップロード"
@@ -565,8 +602,6 @@ desktop/views/components/notes.note.vue:
   detail: "詳細"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
-  hide: "隠す"
-  see-more: "もっと見る"
 desktop/views/components/notes.vue:
   error: "読み込みに失敗しました。"
   retry: "リトライ"
@@ -636,6 +671,9 @@ desktop/views/components/settings.vue:
   behaviour: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
   fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   auto-popout: "ウィンドウの自動ポップアウト"
   auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
   advanced: "詳細設定"
@@ -647,6 +685,7 @@ desktop/views/components/settings.vue:
   delete-wallpaper: "壁紙を削除"
   dark-mode: "ダークモード"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
   post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
   suggest-recent-hashtags: "最近のハッシュタグを投稿フォームに表示する"
@@ -747,7 +786,7 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
   locked-account: "アカウントの保護"
-  is-locked: "投稿を非公開にする"
+  is-locked: "フォローを承認制にする"
   other: "その他"
   is-bot: "このアカウントはBotです"
   is-cat: "このアカウントはCatです"
@@ -861,7 +900,10 @@ desktop/views/pages/welcome.vue:
   signin-button: "やってる"
   signup-button: "やる"
   timeline: "タイムライン"
+  announcements: "お知らせ"
+  photos: "最近の画像"
   powered-by-misskey: "Powered by <b>Misskey</b>."
+  info: "情報"
 desktop/views/pages/drive.vue:
   title: "Misskey Drive"
 desktop/views/pages/favorites.vue:
@@ -998,8 +1040,6 @@ mobile/views/components/friends-maker.vue:
   close: "閉じる"
 mobile/views/components/note.vue:
   reposted-by: "{}がRenote"
-  more: "もっと見る"
-  less: "隠す"
   private: "この投稿は非公開です"
   deleted: "この投稿は削除されました"
   location: "位置情報"
@@ -1128,6 +1168,9 @@ mobile/views/pages/settings/settings.profile.vue:
   avatar: "アイコン"
   banner: "バナー"
   is-cat: "このアカウントはCatです"
+  is-locked: "フォローを承認制にする"
+  advanced: "その他"
+  privacy: "プライバシー"
   save: "保存"
   saved: "プロフィールを保存しました"
   uploading: "アップロード中"
@@ -1149,6 +1192,7 @@ mobile/views/pages/settings.vue:
   dark-mode: "ダークモード"
   i-am-under-limited-internet: "私は通信を制限されている"
   circle-icons: "円形のアイコンを使用"
+  contrasted-acct: "ユーザー名にコントラストを付ける"
   timeline: "タイムライン"
   show-reply-target: "リプライ先を表示する"
   show-my-renotes: "自分の行ったRenoteを表示する"
@@ -1157,8 +1201,14 @@ mobile/views/pages/settings.vue:
   post-style: "投稿の表示スタイル"
   post-style-standard: "標準"
   post-style-smart: "スマート"
+  notification-position: "通知の表示"
+  notification-position-bottom: "下"
+  notification-position-top: "上"
   behavior: "動作"
   fetch-on-scroll: "スクロールで自動読み込み"
+  note-visibility: "投稿の公開範囲"
+  default-note-visibility: "デフォルトの公開範囲"
+  remember-note-visibility: "投稿の公開範囲を記憶する"
   disable-via-mobile: "「モバイルからの投稿」フラグを付けない"
   load-raw-images: "添付された画像を高画質で表示する"
   load-remote-media: "リモートサーバーのメディアを表示する"
@@ -1178,7 +1228,7 @@ mobile/views/pages/settings.vue:
   settings: "設定"
   signout: "サインアウト"
   sound: "サウンド"
-  enableSounds: "サウンドを有効にする"
+  enable-sounds: "サウンドを有効にする"
 mobile/views/pages/user.vue:
   follows-you: "フォローされています"
   following: "フォロー"
diff --git a/package.json b/package.json
index b53345b9d0..bd156e6c23 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
 	"name": "misskey",
 	"author": "syuilo <i@syuilo.com>",
-	"version": "8.25.0",
-	"clientVersion": "1.0.9297",
+	"version": "8.41.0",
+	"clientVersion": "1.0.9716",
 	"codename": "nighthike",
 	"main": "./built/index.js",
 	"private": true,
@@ -55,10 +55,10 @@
 		"@types/koa-send": "4.1.1",
 		"@types/koa-views": "2.0.3",
 		"@types/koa__cors": "2.2.3",
-		"@types/minio": "6.0.2",
+		"@types/minio": "7.0.0",
 		"@types/mkdirp": "0.5.2",
 		"@types/mocha": "5.2.3",
-		"@types/mongodb": "3.1.4",
+		"@types/mongodb": "3.1.7",
 		"@types/ms": "0.7.30",
 		"@types/node": "10.9.4",
 		"@types/portscanner": "2.1.0",
@@ -80,7 +80,7 @@
 		"@types/webpack": "4.4.11",
 		"@types/webpack-stream": "3.2.10",
 		"@types/websocket": "0.0.40",
-		"@types/ws": "6.0.0",
+		"@types/ws": "6.0.1",
 		"animejs": "2.2.0",
 		"autosize": "4.0.2",
 		"autwh": "0.1.0",
@@ -94,14 +94,13 @@
 		"crc-32": "1.2.0",
 		"css-loader": "1.0.0",
 		"dateformat": "3.0.3",
-		"debug": "3.1.0",
+		"debug": "4.0.1",
 		"deep-equal": "1.0.1",
 		"deepcopy": "0.6.3",
 		"diskusage": "0.2.4",
 		"dompurify": "1.0.5",
 		"double-ended-queue": "2.1.0-0",
 		"elasticsearch": "15.1.1",
-		"element-ui": "2.4.6",
 		"emojilib": "2.3.0",
 		"escape-regexp": "0.0.1",
 		"eslint": "5.0.1",
@@ -132,7 +131,6 @@
 		"insert-text-at-cursor": "0.1.1",
 		"is-root": "2.0.0",
 		"is-url": "1.2.4",
-		"jquery": "3.3.1",
 		"js-yaml": "3.12.0",
 		"jsdom": "11.12.0",
 		"koa": "2.5.1",
@@ -151,7 +149,7 @@
 		"lodash.assign": "4.2.0",
 		"mecab-async": "0.1.2",
 		"merge-options": "1.0.1",
-		"minio": "7.0.0",
+		"minio": "7.0.1",
 		"mkdirp": "0.5.1",
 		"mocha": "5.2.0",
 		"moji": "0.5.1",
@@ -160,8 +158,6 @@
 		"ms": "2.1.1",
 		"nan": "2.11.0",
 		"nested-property": "0.0.7",
-		"node-sass": "4.9.3",
-		"node-sass-json-importer": "3.3.1",
 		"nprogress": "0.2.0",
 		"object-assign-deep": "0.4.0",
 		"on-build-webpack": "0.1.0",
@@ -179,6 +175,7 @@
 		"redis": "2.8.0",
 		"request": "2.88.0",
 		"request-promise-native": "1.0.5",
+		"request-stats": "3.0.0",
 		"rimraf": "2.6.2",
 		"rndstr": "1.0.0",
 		"s-age": "1.1.2",
@@ -194,7 +191,7 @@
 		"stylus": "0.54.5",
 		"stylus-loader": "3.0.2",
 		"summaly": "2.2.0",
-		"systeminformation": "3.44.2",
+		"systeminformation": "3.45.6",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
 		"tmp": "0.0.33",
@@ -210,18 +207,19 @@
 		"vue": "2.5.17",
 		"vue-chartjs": "3.4.0",
 		"vue-cropperjs": "2.2.1",
-		"vue-js-modal": "1.3.25",
+		"vue-js-modal": "1.3.26",
 		"vue-json-tree-view": "2.1.4",
-		"vue-loader": "15.4.1",
+		"vue-loader": "15.4.2",
 		"vue-router": "3.0.1",
 		"vue-style-loader": "4.1.2",
 		"vue-template-compiler": "2.5.17",
 		"vuedraggable": "2.16.0",
+		"vuewordcloud": "18.7.11",
 		"vuex": "3.0.1",
 		"vuex-persistedstate": "2.5.4",
 		"web-push": "3.3.2",
 		"webfinger.js": "2.6.6",
-		"webpack": "4.17.2",
+		"webpack": "4.19.0",
 		"webpack-cli": "3.1.0",
 		"websocket": "1.0.26",
 		"ws": "6.0.0",
diff --git a/src/client/app/boot.js b/src/client/app/boot.js
index dd2cf93a89..25aa26dd19 100644
--- a/src/client/app/boot.js
+++ b/src/client/app/boot.js
@@ -18,6 +18,8 @@
 		return;
 	}
 
+	const langs = LANGS;
+
 	//#region Load settings
 	let settings = null;
 	const vuex = localStorage.getItem('vuex');
@@ -40,10 +42,10 @@
 	//#region Detect the user language
 	let lang = null;
 
-	if (LANGS.includes(navigator.language)) {
+	if (langs.includes(navigator.language)) {
 		lang = navigator.language;
 	} else {
-		lang = LANGS.find(x => x.split('-')[0] == navigator.language);
+		lang = langs.find(x => x.split('-')[0] == navigator.language);
 
 		if (lang == null) {
 			// Fallback
@@ -52,7 +54,7 @@
 	}
 
 	if (settings && settings.device.lang &&
-		LANGS.includes(settings.device.lang)) {
+		langs.includes(settings.device.lang)) {
 		lang = settings.device.lang;
 	}
 	//#endregion
@@ -140,7 +142,7 @@
 		// Random
 		localStorage.setItem('salt', Math.random().toString());
 
-		// Clear cache (serive worker)
+		// Clear cache (service worker)
 		try {
 			navigator.serviceWorker.controller.postMessage('clear');
 
diff --git a/src/client/app/common/scripts/check-for-update.ts b/src/client/app/common/scripts/check-for-update.ts
index 4445eefc39..91b165b45d 100644
--- a/src/client/app/common/scripts/check-for-update.ts
+++ b/src/client/app/common/scripts/check-for-update.ts
@@ -9,7 +9,7 @@ export default async function(mios: MiOS, force = false, silent = false) {
 		localStorage.setItem('should-refresh', 'true');
 		localStorage.setItem('v', newer);
 
-		// Clear cache (serive worker)
+		// Clear cache (service worker)
 		try {
 			if (navigator.serviceWorker.controller) {
 				navigator.serviceWorker.controller.postMessage('clear');
diff --git a/src/client/app/common/scripts/gcd.ts b/src/client/app/common/scripts/gcd.ts
deleted file mode 100644
index 9a19f9da66..0000000000
--- a/src/client/app/common/scripts/gcd.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-const gcd = (a, b) => !b ? a : gcd(b, a % b);
-export default gcd;
diff --git a/src/client/app/common/scripts/parse-search-query.ts b/src/client/app/common/scripts/parse-search-query.ts
deleted file mode 100644
index 5f6ae3320a..0000000000
--- a/src/client/app/common/scripts/parse-search-query.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-export default function(qs: string) {
-	const q = {
-		text: ''
-	};
-
-	qs.split(' ').forEach(x => {
-		if (/^([a-z_]+?):(.+?)$/.test(x)) {
-			const [key, value] = x.split(':');
-			switch (key) {
-				case 'user':
-					q['includeUserUsernames'] = value.split(',');
-					break;
-				case 'exclude_user':
-					q['excludeUserUsernames'] = value.split(',');
-					break;
-				case 'follow':
-					q['following'] = value == 'null' ? null : value == 'true';
-					break;
-				case 'reply':
-					q['reply'] = value == 'null' ? null : value == 'true';
-					break;
-				case 'renote':
-					q['renote'] = value == 'null' ? null : value == 'true';
-					break;
-				case 'media':
-					q['media'] = value == 'null' ? null : value == 'true';
-					break;
-				case 'poll':
-					q['poll'] = value == 'null' ? null : value == 'true';
-					break;
-				case 'until':
-				case 'since':
-					// YYYY-MM-DD
-					if (/^[0-9]+\-[0-9]+\-[0-9]+$/) {
-						const [yyyy, mm, dd] = value.split('-');
-						q[`${key}_date`] = (new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10))).getTime();
-					}
-					break;
-				default:
-					q[key] = value;
-					break;
-			}
-		} else {
-			q.text += x + ' ';
-		}
-	});
-
-	if (q.text) {
-		q.text = q.text.trim();
-	}
-
-	return q;
-}
diff --git a/src/client/app/common/scripts/streaming/stream-manager.ts b/src/client/app/common/scripts/streaming/stream-manager.ts
index 568b8b0372..8dd06f67d3 100644
--- a/src/client/app/common/scripts/streaming/stream-manager.ts
+++ b/src/client/app/common/scripts/streaming/stream-manager.ts
@@ -1,6 +1,7 @@
 import { EventEmitter } from 'eventemitter3';
 import * as uuid from 'uuid';
 import Connection from './stream';
+import { erase } from '../../../../../prelude/array';
 
 /**
  * ストリーム接続を管理するクラス
@@ -89,7 +90,7 @@ export default abstract class StreamManager<T extends Connection> extends EventE
 	 * @param userId use で発行したユーザーID
 	 */
 	public dispose(userId) {
-		this.users = this.users.filter(id => id != userId);
+		this.users = erase(userId, this.users);
 
 		this._connection.user = `Managed (${ this.users.length })`;
 
diff --git a/src/client/app/common/views/components/acct.vue b/src/client/app/common/views/components/acct.vue
index 1ad222afdd..542fbb4296 100644
--- a/src/client/app/common/views/components/acct.vue
+++ b/src/client/app/common/views/components/acct.vue
@@ -1,19 +1,25 @@
 <template>
 <span class="mk-acct">
 	<span class="name">@{{ user.username }}</span>
-	<span class="host" v-if="user.host">@{{ user.host }}</span>
+	<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="user.host || detail || $store.state.settings.showFullAcct">@{{ user.host || host }}</span>
 </span>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
+import { host } from '../../../config';
 export default Vue.extend({
-	props: ['user']
+	props: ['user', 'detail'],
+	data() {
+		return {
+			host
+		};
+	}
 });
 </script>
 
 <style lang="stylus" scoped>
 .mk-acct
-	> .host
+	> .host.fade
 		opacity 0.5
 </style>
diff --git a/src/client/app/common/views/components/avatar.vue b/src/client/app/common/views/components/avatar.vue
index c5ac74e537..a2b0fc6bd3 100644
--- a/src/client/app/common/views/components/avatar.vue
+++ b/src/client/app/common/views/components/avatar.vue
@@ -1,15 +1,15 @@
 <template>
-	<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
-		<span class="inner" :style="style"></span>
+	<span class="mk-avatar" :style="style" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
+		<span class="inner" :style="icon"></span>
 	</span>
-	<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
-		<span class="inner" :style="style"></span>
+	<span class="mk-avatar" :style="style" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
+		<span class="inner" :style="icon"></span>
 	</span>
-	<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
-		<span class="inner" :style="style"></span>
+	<router-link class="mk-avatar" :style="style" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
+		<span class="inner" :style="icon"></span>
 	</router-link>
-	<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
-		<span class="inner" :style="style"></span>
+	<router-link class="mk-avatar" :style="style" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
+		<span class="inner" :style="icon"></span>
 	</router-link>
 </template>
 
@@ -42,6 +42,11 @@ export default Vue.extend({
 			return this.user.isCat && this.$store.state.settings.circleIcons;
 		},
 		style(): any {
+			return {
+				borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
+			};
+		},
+		icon(): any {
 			return {
 				backgroundColor: this.lightmode
 					? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
diff --git a/src/client/app/common/views/components/cw-button.vue b/src/client/app/common/views/components/cw-button.vue
new file mode 100644
index 0000000000..06087edc93
--- /dev/null
+++ b/src/client/app/common/views/components/cw-button.vue
@@ -0,0 +1,44 @@
+<template>
+<button class="nrvgflfuaxwgkxoynpnumyookecqrrvh" @click="toggle">{{ value ? '%i18n:@hide%' : '%i18n:@show%' }}</button>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+	props: {
+		value: {
+			type: Boolean,
+			required: true
+		}
+	},
+
+	methods: {
+		toggle() {
+			this.$emit('input', !this.value);
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+root(isDark)
+	display inline-block
+	padding 4px 8px
+	font-size 0.7em
+	color isDark ? #393f4f : #fff
+	background isDark ? #687390 : #b1b9c1
+	border-radius 2px
+	cursor pointer
+	user-select none
+
+	&:hover
+		background isDark ? #707b97 : #bbc4ce
+
+.nrvgflfuaxwgkxoynpnumyookecqrrvh[data-darkmode]
+	root(true)
+
+.nrvgflfuaxwgkxoynpnumyookecqrrvh:not([data-darkmode])
+	root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue
index 673879a435..fea19d917e 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.game.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue
@@ -50,15 +50,15 @@
 	</div>
 
 	<div class="player" v-if="game.isEnded">
-		<el-button-group>
-			<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button>
-			<el-button type="primary" @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</el-button>
-		</el-button-group>
+		<div>
+			<button @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</button>
+			<button @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</button>
+		</div>
 		<span>{{ logPos }} / {{ logs.length }}</span>
-		<el-button-group>
-			<el-button type="primary" @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</el-button>
-			<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
-		</el-button-group>
+		<div>
+			<button @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</button>
+			<button @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</button>
+		</div>
 	</div>
 
 	<div class="info">
diff --git a/src/client/app/common/views/components/games/reversi/reversi.index.vue b/src/client/app/common/views/components/games/reversi/reversi.index.vue
index fa88aeaaf4..d23902aae7 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.index.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.index.vue
@@ -3,7 +3,6 @@
 	<h1>%i18n:@title%</h1>
 	<p>%i18n:@sub-title%</p>
 	<div class="play">
-		<!--<el-button round>フリーマッチ(準備中)</el-button>-->
 		<form-button primary round @click="match">%i18n:@invite%</form-button>
 		<details>
 			<summary>%i18n:@rule%</summary>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue
index aed8718dd0..fef833d63e 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.room.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue
@@ -59,11 +59,6 @@
 			</header>
 
 			<div>
-				<el-alert v-for="message in messages"
-						:title="message.text"
-						:type="message.type"
-						:key="message.id"/>
-
 				<template v-for="item in form">
 					<mk-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</mk-switch>
 
@@ -93,7 +88,7 @@
 						</header>
 
 						<div>
-							<el-input v-model="item.value" @change="onChangeForm(item)"/>
+							<input v-model="item.value" @change="onChangeForm(item)"/>
 						</div>
 					</div>
 				</template>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index 43cde6c54f..21af4ec846 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -1,5 +1,7 @@
 import Vue from 'vue';
 
+import cwButton from './cw-button.vue';
+import tagCloud from './tag-cloud.vue';
 import trends from './trends.vue';
 import analogClock from './analog-clock.vue';
 import menu from './menu.vue';
@@ -42,6 +44,8 @@ import uiSelect from './ui/select.vue';
 import formButton from './ui/form/button.vue';
 import formRadio from './ui/form/radio.vue';
 
+Vue.component('mk-cw-button', cwButton);
+Vue.component('mk-tag-cloud', tagCloud);
 Vue.component('mk-trends', trends);
 Vue.component('mk-analog-clock', analogClock);
 Vue.component('mk-menu', menu);
diff --git a/src/client/app/common/views/components/menu.vue b/src/client/app/common/views/components/menu.vue
index e99bfcbd26..fba7e235e0 100644
--- a/src/client/app/common/views/components/menu.vue
+++ b/src/client/app/common/views/components/menu.vue
@@ -108,7 +108,7 @@ export default Vue.extend({
 				easing: 'easeInBack',
 				complete: () => {
 					this.$emit('closed');
-					this.$destroy();
+					this.destroyDom();
 				}
 			});
 		}
diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.ts b/src/client/app/common/views/components/misskey-flavored-markdown.ts
index 44680751f7..224bd6f5de 100644
--- a/src/client/app/common/views/components/misskey-flavored-markdown.ts
+++ b/src/client/app/common/views/components/misskey-flavored-markdown.ts
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { VNode } from 'vue';
 import * as emojilib from 'emojilib';
 import { length } from 'stringz';
 import parse from '../../../../../mfm/parse';
@@ -6,10 +6,7 @@ import getAcct from '../../../../../misc/acct/render';
 import { url } from '../../../config';
 import MkUrl from './url.vue';
 import MkGoogle from './google.vue';
-
-const flatten = list => list.reduce(
-	(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
-);
+import { concat } from '../../../../../prelude/array';
 
 export default Vue.component('misskey-flavored-markdown', {
 	props: {
@@ -32,20 +29,20 @@ export default Vue.component('misskey-flavored-markdown', {
 	},
 
 	render(createElement) {
-		let ast;
+		let ast: any[];
 
 		if (this.ast == null) {
 			// Parse text to ast
 			ast = parse(this.text);
 		} else {
-			ast = this.ast;
+			ast = this.ast as any[];
 		}
 
 		let bigCount = 0;
 		let motionCount = 0;
 
 		// Parse ast to DOM
-		const els = flatten(ast.map(token => {
+		const els = concat(ast.map((token): VNode[] => {
 			switch (token.type) {
 				case 'text': {
 					const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
@@ -56,12 +53,12 @@ export default Vue.component('misskey-flavored-markdown', {
 						x[x.length - 1].pop();
 						return x;
 					} else {
-						return createElement('span', text.replace(/\n/g, ' '));
+						return [createElement('span', text.replace(/\n/g, ' '))];
 					}
 				}
 
 				case 'bold': {
-					return createElement('b', token.bold);
+					return [createElement('b', token.bold)];
 				}
 
 				case 'big': {
@@ -95,23 +92,23 @@ export default Vue.component('misskey-flavored-markdown', {
 				}
 
 				case 'url': {
-					return createElement(MkUrl, {
+					return [createElement(MkUrl, {
 						props: {
 							url: token.content,
 							target: '_blank'
 						}
-					});
+					})];
 				}
 
 				case 'link': {
-					return createElement('a', {
+					return [createElement('a', {
 						attrs: {
 							class: 'link',
 							href: token.url,
 							target: '_blank',
 							title: token.url
 						}
-					}, token.title);
+					}, token.title)];
 				}
 
 				case 'mention': {
@@ -129,16 +126,16 @@ export default Vue.component('misskey-flavored-markdown', {
 				}
 
 				case 'hashtag': {
-					return createElement('a', {
+					return [createElement('a', {
 						attrs: {
 							href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
 							target: '_blank'
 						}
-					}, token.content);
+					}, token.content)];
 				}
 
 				case 'code': {
-					return createElement('pre', {
+					return [createElement('pre', {
 						class: 'code'
 					}, [
 						createElement('code', {
@@ -146,15 +143,15 @@ export default Vue.component('misskey-flavored-markdown', {
 								innerHTML: token.html
 							}
 						})
-					]);
+					])];
 				}
 
 				case 'inline-code': {
-					return createElement('code', {
+					return [createElement('code', {
 						domProps: {
 							innerHTML: token.html
 						}
-					});
+					})];
 				}
 
 				case 'quote': {
@@ -164,43 +161,45 @@ export default Vue.component('misskey-flavored-markdown', {
 						const x = text2.split('\n')
 							.map(t => [createElement('span', t), createElement('br')]);
 						x[x.length - 1].pop();
-						return createElement('div', {
+						return [createElement('div', {
 							attrs: {
 								class: 'quote'
 							}
-						}, x);
+						}, x)];
 					} else {
-						return createElement('span', {
+						return [createElement('span', {
 							attrs: {
 								class: 'quote'
 							}
-						}, text2.replace(/\n/g, ' '));
+						}, text2.replace(/\n/g, ' '))];
 					}
 				}
 
 				case 'title': {
-					return createElement('div', {
+					return [createElement('div', {
 						attrs: {
 							class: 'title'
 						}
-					}, token.title);
+					}, token.title)];
 				}
 
 				case 'emoji': {
 					const emoji = emojilib.lib[token.emoji];
-					return createElement('span', emoji ? emoji.char : token.content);
+					return [createElement('span', emoji ? emoji.char : token.content)];
 				}
 
 				case 'search': {
-					return createElement(MkGoogle, {
+					return [createElement(MkGoogle, {
 						props: {
 							q: token.query
 						}
-					});
+					})];
 				}
 
 				default: {
 					console.log('unknown ast type:', token.type);
+
+					return [];
 				}
 			}
 		}));
diff --git a/src/client/app/common/views/components/note-menu.vue b/src/client/app/common/views/components/note-menu.vue
index 0b0609ac4e..c9912fb1e2 100644
--- a/src/client/app/common/views/components/note-menu.vue
+++ b/src/client/app/common/views/components/note-menu.vue
@@ -64,7 +64,7 @@ export default Vue.extend({
 			(this as any).api('i/pin', {
 				noteId: this.note.id
 			}).then(() => {
-				this.$destroy();
+				this.destroyDom();
 			});
 		},
 
@@ -73,7 +73,7 @@ export default Vue.extend({
 			(this as any).api('notes/delete', {
 				noteId: this.note.id
 			}).then(() => {
-				this.$destroy();
+				this.destroyDom();
 			});
 		},
 
@@ -81,13 +81,13 @@ export default Vue.extend({
 			(this as any).api('notes/favorites/create', {
 				noteId: this.note.id
 			}).then(() => {
-				this.$destroy();
+				this.destroyDom();
 			});
 		},
 
 		closed() {
 			this.$nextTick(() => {
-				this.$destroy();
+				this.destroyDom();
 			});
 		}
 	}
diff --git a/src/client/app/common/views/components/poll-editor.vue b/src/client/app/common/views/components/poll-editor.vue
index 115c934c8b..30d9799fec 100644
--- a/src/client/app/common/views/components/poll-editor.vue
+++ b/src/client/app/common/views/components/poll-editor.vue
@@ -20,6 +20,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import { erase } from '../../../../../prelude/array';
 export default Vue.extend({
 	data() {
 		return {
@@ -53,7 +54,7 @@ export default Vue.extend({
 
 		get() {
 			return {
-				choices: this.choices.filter(choice => choice != '')
+				choices: erase('', this.choices)
 			}
 		},
 
diff --git a/src/client/app/common/views/components/poll.vue b/src/client/app/common/views/components/poll.vue
index 660247edbc..4fe51d219b 100644
--- a/src/client/app/common/views/components/poll.vue
+++ b/src/client/app/common/views/components/poll.vue
@@ -21,6 +21,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import { sum } from '../../../../../prelude/array';
 export default Vue.extend({
 	props: ['note'],
 	data() {
@@ -33,7 +34,7 @@ export default Vue.extend({
 			return this.note.poll;
 		},
 		total(): number {
-			return this.poll.choices.reduce((a, b) => a + b.votes, 0);
+			return sum(this.poll.choices.map(x => x.votes));
 		},
 		isVoted(): boolean {
 			return this.poll.choices.some(c => c.isVoted);
diff --git a/src/client/app/common/views/components/reaction-icon.vue b/src/client/app/common/views/components/reaction-icon.vue
index 46886b8ab2..c668efac6b 100644
--- a/src/client/app/common/views/components/reaction-icon.vue
+++ b/src/client/app/common/views/components/reaction-icon.vue
@@ -1,17 +1,17 @@
 <template>
 <span class="mk-reaction-icon">
-	<img v-if="reaction == 'like'" src="/assets/reactions/like.png" alt="%i18n:common.reactions.like%">
-	<img v-if="reaction == 'love'" src="/assets/reactions/love.png" alt="%i18n:common.reactions.love%">
-	<img v-if="reaction == 'laugh'" src="/assets/reactions/laugh.png" alt="%i18n:common.reactions.laugh%">
-	<img v-if="reaction == 'hmm'" src="/assets/reactions/hmm.png" alt="%i18n:common.reactions.hmm%">
-	<img v-if="reaction == 'surprise'" src="/assets/reactions/surprise.png" alt="%i18n:common.reactions.surprise%">
-	<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
-	<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
-	<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
-	<img v-if="reaction == 'rip'" src="/assets/reactions/rip.png" alt="%i18n:common.reactions.rip%">
+	<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" alt="%i18n:common.reactions.like%">
+	<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" alt="%i18n:common.reactions.love%">
+	<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" alt="%i18n:common.reactions.laugh%">
+	<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" alt="%i18n:common.reactions.hmm%">
+	<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" alt="%i18n:common.reactions.surprise%">
+	<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" alt="%i18n:common.reactions.congrats%">
+	<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" alt="%i18n:common.reactions.angry%">
+	<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" alt="%i18n:common.reactions.confused%">
+	<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" alt="%i18n:common.reactions.rip%">
 	<template v-if="reaction == 'pudding'">
-		<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
-		<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
+		<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" alt="%i18n:common.reactions.pudding%">
+		<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" alt="%i18n:common.reactions.pudding%">
 	</template>
 </span>
 </template>
diff --git a/src/client/app/common/views/components/reaction-picker.vue b/src/client/app/common/views/components/reaction-picker.vue
index a455afbf7d..a4828c987b 100644
--- a/src/client/app/common/views/components/reaction-picker.vue
+++ b/src/client/app/common/views/components/reaction-picker.vue
@@ -95,7 +95,7 @@ export default Vue.extend({
 				reaction: reaction
 			}).then(() => {
 				if (this.cb) this.cb();
-				this.$destroy();
+				this.destroyDom();
 			});
 		},
 		onMouseover(e) {
@@ -120,7 +120,7 @@ export default Vue.extend({
 				scale: 0.5,
 				duration: 200,
 				easing: 'easeInBack',
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		}
 	}
diff --git a/src/client/app/common/views/components/tag-cloud.vue b/src/client/app/common/views/components/tag-cloud.vue
new file mode 100644
index 0000000000..5f2cc5276a
--- /dev/null
+++ b/src/client/app/common/views/components/tag-cloud.vue
@@ -0,0 +1,90 @@
+<template>
+<div class="jtivnzhfwquxpsfidertopbmwmchmnmo">
+	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
+	<p class="empty" v-else-if="tags.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
+	<div v-else>
+		<vue-word-cloud
+				:words="tags.slice(0, 20).map(x => [x.name, x.count])"
+				:color="color"
+				:spacing="1">
+			<template slot-scope="{word, text, weight}">
+				<div style="cursor: pointer;" :title="weight">
+					{{ text }}
+				</div>
+			</template>
+		</vue-word-cloud>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import * as VueWordCloud from 'vuewordcloud';
+
+export default Vue.extend({
+	components: {
+		[VueWordCloud.name]: VueWordCloud
+	},
+	data() {
+		return {
+			tags: [],
+			fetching: true,
+			clock: null
+		};
+	},
+	mounted() {
+		this.fetch();
+		this.clock = setInterval(this.fetch, 1000 * 60);
+	},
+	beforeDestroy() {
+		clearInterval(this.clock);
+	},
+	methods: {
+		fetch() {
+			(this as any).api('aggregation/hashtags').then(tags => {
+				this.tags = tags;
+				this.fetching = false;
+			});
+		},
+		color([, weight]) {
+			const peak = Math.max.apply(null, this.tags.map(x => x.count));
+			const w = weight / peak;
+
+			if (w > 0.9) {
+				return this.$store.state.device.darkmode ? '#ff4e69' : '#ff4e69';
+			} else if (w > 0.5) {
+				return this.$store.state.device.darkmode ? '#3bc4c7' : '#3bc4c7';
+			} else {
+				return this.$store.state.device.darkmode ? '#fff' : '#555';
+			}
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+root(isDark)
+	height 100%
+	width 100%
+
+	> .fetching
+	> .empty
+		margin 0
+		padding 16px
+		text-align center
+		color #aaa
+
+		> [data-fa]
+			margin-right 4px
+
+	> div
+		height 100%
+		width 100%
+
+.jtivnzhfwquxpsfidertopbmwmchmnmo[data-darkmode]
+	root(true)
+
+.jtivnzhfwquxpsfidertopbmwmchmnmo:not([data-darkmode])
+	root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/card.vue b/src/client/app/common/views/components/ui/card.vue
index 05c51bca6b..aa16b557e1 100644
--- a/src/client/app/common/views/components/ui/card.vue
+++ b/src/client/app/common/views/components/ui/card.vue
@@ -24,19 +24,34 @@ export default Vue.extend({
 
 root(isDark)
 	margin 16px
-	padding 16px
 	color isDark ? #fff : #000
 	background isDark ? #282C37 : #fff
 	box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
 
-	@media (min-width 500px)
-		padding 32px
-
 	> header
-		font-weight normal
-		font-size 24px
+		padding 16px
+		font-weight bold
+		font-size 20px
 		color isDark ? #fff : #444
 
+		@media (min-width 500px)
+			padding 24px 32px
+
+	> section
+		padding 20px 16px
+		border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1)
+
+		@media (min-width 500px)
+			padding 32px
+
+		&.fit-top
+			padding-top 0
+
+		> header
+			margin-bottom 16px
+			font-weight bold
+			color isDark ? #fff : #444
+
 .ui-card[data-darkmode]
 	root(true)
 
diff --git a/src/client/app/common/views/components/ui/radio.vue b/src/client/app/common/views/components/ui/radio.vue
index 04a46c5a96..dcdda1cf0e 100644
--- a/src/client/app/common/views/components/ui/radio.vue
+++ b/src/client/app/common/views/components/ui/radio.vue
@@ -55,7 +55,7 @@ export default Vue.extend({
 
 root(isDark)
 	display inline-block
-	margin 32px 32px 32px 0
+	margin 0 32px 0 0
 	cursor pointer
 	transition all 0.3s
 
diff --git a/src/client/app/common/views/components/ui/switch.vue b/src/client/app/common/views/components/ui/switch.vue
index a9e00d73d2..e88b867801 100644
--- a/src/client/app/common/views/components/ui/switch.vue
+++ b/src/client/app/common/views/components/ui/switch.vue
@@ -64,6 +64,12 @@ root(isDark)
 	cursor pointer
 	transition all 0.3s
 
+	&:first-child
+		margin-top 0
+
+	&:last-child
+		margin-bottom 0
+
 	> *
 		user-select none
 
@@ -89,6 +95,7 @@ root(isDark)
 
 	> .button
 		display inline-block
+		flex-shrink 0
 		margin 3px 0 0 0
 		width 34px
 		height 14px
diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue
index e182e7f8cb..f9b8415b5b 100644
--- a/src/client/app/common/views/components/url-preview.vue
+++ b/src/client/app/common/views/components/url-preview.vue
@@ -14,7 +14,7 @@
 			<header>
 				<h1>{{ title }}</h1>
 			</header>
-			<p>{{ description }}</p>
+			<p>{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p>
 			<footer>
 				<img class="icon" v-if="icon" :src="icon"/>
 				<p>{{ sitename }}</p>
diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue
index 4691604e57..1830b1832e 100644
--- a/src/client/app/common/views/components/visibility-chooser.vue
+++ b/src/client/app/common/views/components/visibility-chooser.vue
@@ -47,7 +47,7 @@ export default Vue.extend({
 	props: ['source', 'compact'],
 	data() {
 		return {
-			v: this.$store.state.device.visibility || 'public'
+			v: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility
 		}
 	},
 	mounted() {
@@ -97,9 +97,11 @@ export default Vue.extend({
 	},
 	methods: {
 		choose(visibility) {
-			this.$store.commit('device/setVisibility', visibility);
+			if (this.$store.state.settings.rememberNoteVisibility) {
+				this.$store.commit('device/setVisibility', visibility);
+			}
 			this.$emit('chosen', visibility);
-			this.$destroy();
+			this.destroyDom();
 		},
 		close() {
 			(this.$refs.backdrop as any).style.pointerEvents = 'none';
@@ -117,7 +119,7 @@ export default Vue.extend({
 				scale: 0.5,
 				duration: 200,
 				easing: 'easeInBack',
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		}
 	}
diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue
index d4e7902c7b..965ec78559 100644
--- a/src/client/app/common/views/components/welcome-timeline.vue
+++ b/src/client/app/common/views/components/welcome-timeline.vue
@@ -1,22 +1,24 @@
 <template>
 <div class="mk-welcome-timeline">
-	<div v-for="note in notes">
-		<mk-avatar class="avatar" :user="note.user" target="_blank"/>
-		<div class="body">
-			<header>
-				<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
-				<span class="username">@{{ note.user | acct }}</span>
-				<div class="info">
-					<router-link class="created-at" :to="note | notePage">
-						<mk-time :time="note.createdAt"/>
-					</router-link>
+	<transition-group name="ldzpakcixzickvggyixyrhqwjaefknon" tag="div">
+		<div v-for="note in notes" :key="note.id">
+			<mk-avatar class="avatar" :user="note.user" target="_blank"/>
+			<div class="body">
+				<header>
+					<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
+					<span class="username">@{{ note.user | acct }}</span>
+					<div class="info">
+						<router-link class="created-at" :to="note | notePage">
+							<mk-time :time="note.createdAt"/>
+						</router-link>
+					</div>
+				</header>
+				<div class="text">
+					<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
 				</div>
-			</header>
-			<div class="text">
-				<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
 			</div>
 		</div>
-	</div>
+	</transition-group>
 </div>
 </template>
 
@@ -63,7 +65,7 @@ export default Vue.extend({
 				local: true,
 				reply: false,
 				renote: false,
-				media: false,
+				file: false,
 				poll: false
 			}).then(notes => {
 				this.notes = notes;
@@ -83,64 +85,73 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
+.ldzpakcixzickvggyixyrhqwjaefknon-enter
+.ldzpakcixzickvggyixyrhqwjaefknon-leave-to
+	opacity 0
+	transform translateY(-30px)
+
 root(isDark)
 	background isDark ? #282C37 : #fff
 
 	> div
-		padding 16px
-		overflow-wrap break-word
-		font-size .9em
-		color isDark ? #fff : #4C4C4C
-		border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
+		> *
+			transition transform .3s ease, opacity .3s ease
 
-		&:after
-			content ""
-			display block
-			clear both
+		> div
+			padding 16px
+			overflow-wrap break-word
+			font-size .9em
+			color isDark ? #fff : #4C4C4C
+			border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
 
-		> .avatar
-			display block
-			float left
-			position -webkit-sticky
-			position sticky
-			top 16px
-			width 42px
-			height 42px
-			border-radius 6px
+			&:after
+				content ""
+				display block
+				clear both
 
-		> .body
-			float right
-			width calc(100% - 42px)
-			padding-left 12px
+			> .avatar
+				display block
+				float left
+				position -webkit-sticky
+				position sticky
+				top 16px
+				width 42px
+				height 42px
+				border-radius 6px
 
-			> header
-				display flex
-				align-items center
-				margin-bottom 4px
-				white-space nowrap
+			> .body
+				float right
+				width calc(100% - 42px)
+				padding-left 12px
 
-				> .name
-					display block
-					margin 0 .5em 0 0
-					padding 0
-					overflow hidden
-					font-weight bold
-					text-overflow ellipsis
-					color isDark ? #fff : #627079
+				> header
+					display flex
+					align-items center
+					margin-bottom 4px
+					white-space nowrap
 
-				> .username
-					margin 0 .5em 0 0
-					color isDark ? #606984 : #ccc
+					> .name
+						display block
+						margin 0 .5em 0 0
+						padding 0
+						overflow hidden
+						font-weight bold
+						text-overflow ellipsis
+						color isDark ? #fff : #627079
 
-				> .info
-					margin-left auto
-					font-size 0.9em
+					> .username
+						margin 0 .5em 0 0
+						color isDark ? #606984 : #ccc
 
-					> .created-at
-						color isDark ? #606984 : #c0c0c0
+					> .info
+						margin-left auto
+						font-size 0.9em
 
-			> .text
-				text-align left
+						> .created-at
+							color isDark ? #606984 : #c0c0c0
+
+				> .text
+					text-align left
 
 .mk-welcome-timeline[data-darkmode]
 	root(true)
diff --git a/src/client/app/common/views/directives/autocomplete.ts b/src/client/app/common/views/directives/autocomplete.ts
index 26bc13871d..f7f8e9bf16 100644
--- a/src/client/app/common/views/directives/autocomplete.ts
+++ b/src/client/app/common/views/directives/autocomplete.ts
@@ -167,7 +167,7 @@ class Autocomplete {
 	private close() {
 		if (this.suggestion == null) return;
 
-		this.suggestion.$destroy();
+		this.suggestion.destroyDom();
 		this.suggestion = null;
 
 		this.textarea.focus();
diff --git a/src/client/app/common/views/pages/follow.vue b/src/client/app/common/views/pages/follow.vue
index 05c1329f6d..80a870a257 100644
--- a/src/client/app/common/views/pages/follow.vue
+++ b/src/client/app/common/views/pages/follow.vue
@@ -32,7 +32,6 @@
 <script lang="ts">
 import Vue from 'vue';
 import parseAcct from '../../../../../misc/acct/parse';
-import getUserName from '../../../../../misc/get-user-name';
 import Progress from '../../../common/scripts/loading';
 
 export default Vue.extend({
diff --git a/src/client/app/common/views/widgets/analog-clock.vue b/src/client/app/common/views/widgets/analog-clock.vue
index 0de30228b3..04223f0d21 100644
--- a/src/client/app/common/views/widgets/analog-clock.vue
+++ b/src/client/app/common/views/widgets/analog-clock.vue
@@ -1,8 +1,8 @@
 <template>
 <div class="mkw-analog-clock">
-	<mk-widget-container :naked="!(props.design % 2)" :show-header="false">
+	<mk-widget-container :naked="props.style % 2 === 0" :show-header="false">
 		<div class="mkw-analog-clock--body">
-			<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="!(props.design && ~props.design)"/>
+			<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/>
 		</div>
 	</mk-widget-container>
 </div>
@@ -13,13 +13,12 @@ import define from '../../../common/define-widget';
 export default define({
 	name: 'analog-clock',
 	props: () => ({
-		design: -1
+		style: 0
 	})
 }).extend({
 	methods: {
 		func() {
-			if (++this.props.design > 2)
-				this.props.design = -1;
+			this.props.style = (this.props.style + 1) % 4;
 			this.save();
 		}
 	}
diff --git a/src/client/app/common/views/widgets/broadcast.vue b/src/client/app/common/views/widgets/broadcast.vue
index e4e77263e5..f2fa720f52 100644
--- a/src/client/app/common/views/widgets/broadcast.vue
+++ b/src/client/app/common/views/widgets/broadcast.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="anltbovirfeutcigvwgmgxipejaeozxi"
-	:data-found="broadcasts.length != 0"
+	:data-found="announcements && announcements.length != 0"
 	:data-melt="props.design == 1"
 	:data-mobile="platform == 'mobile'"
 >
@@ -14,12 +14,12 @@
 		</svg>
 	</div>
 	<p class="fetching" v-if="fetching">%i18n:@fetching%<mk-ellipsis/></p>
-	<h1 v-if="!fetching">{{ broadcasts.length == 0 ? '%i18n:@no-broadcasts%' : broadcasts[i].title }}</h1>
+	<h1 v-if="!fetching">{{ announcements.length == 0 ? '%i18n:@no-broadcasts%' : announcements[i].title }}</h1>
 	<p v-if="!fetching">
-		<span v-if="broadcasts.length != 0" v-html="broadcasts[i].text"></span>
-		<template v-if="broadcasts.length == 0">%i18n:@have-a-nice-day%</template>
+		<span v-if="announcements.length != 0" v-html="announcements[i].text"></span>
+		<template v-if="announcements.length == 0">%i18n:@have-a-nice-day%</template>
 	</p>
-	<a v-if="broadcasts.length > 1" @click="next">%i18n:@next% &gt;&gt;</a>
+	<a v-if="announcements.length > 1" @click="next">%i18n:@next% &gt;&gt;</a>
 </div>
 </template>
 
@@ -36,18 +36,18 @@ export default define({
 		return {
 			i: 0,
 			fetching: true,
-			broadcasts: []
+			announcements: []
 		};
 	},
 	mounted() {
 		(this as any).os.getMeta().then(meta => {
-			this.broadcasts = meta.broadcasts;
+			this.announcements = meta.broadcasts;
 			this.fetching = false;
 		});
 	},
 	methods: {
 		next() {
-			if (this.i == this.broadcasts.length - 1) {
+			if (this.i == this.announcements.length - 1) {
 				this.i = 0;
 			} else {
 				this.i++;
@@ -126,7 +126,7 @@ root(isDark)
 		margin 0
 		font-size 0.95em
 		font-weight normal
-		color #4078c0
+		color isDark ? #539eff : #4078c0
 
 	> p
 		display block
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index f0e8a42662..e32682286c 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -6,7 +6,6 @@ import VueRouter from 'vue-router';
 
 // Style
 import './style.styl';
-import '../../element.scss';
 
 import init from '../init';
 import fuckAdBlock from '../common/scripts/fuck-ad-block';
diff --git a/src/client/app/desktop/views/components/charts.vue b/src/client/app/desktop/views/components/charts.vue
index c4e92e429f..6514cdf788 100644
--- a/src/client/app/desktop/views/components/charts.vue
+++ b/src/client/app/desktop/views/components/charts.vue
@@ -19,6 +19,11 @@
 				<option value="drive">%i18n:@charts.drive%</option>
 				<option value="drive-total">%i18n:@charts.drive-total%</option>
 			</optgroup>
+			<optgroup label="%i18n:@network%">
+				<option value="network-requests">%i18n:@charts.network-requests%</option>
+				<option value="network-time">%i18n:@charts.network-time%</option>
+				<option value="network-usage">%i18n:@charts.network-usage%</option>
+			</optgroup>
 		</select>
 		<div>
 			<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span>
@@ -41,7 +46,10 @@ const colors = {
 	localPlus: 'rgb(52, 178, 118)',
 	remotePlus: 'rgb(158, 255, 209)',
 	localMinus: 'rgb(255, 97, 74)',
-	remoteMinus: 'rgb(255, 149, 134)'
+	remoteMinus: 'rgb(255, 149, 134)',
+
+	incoming: 'rgb(52, 178, 118)',
+	outgoing: 'rgb(255, 97, 74)',
 };
 
 const rgba = (color: string): string => {
@@ -75,6 +83,9 @@ export default Vue.extend({
 				case 'drive-total': return this.driveTotalChart();
 				case 'drive-files': return this.driveFilesChart();
 				case 'drive-files-total': return this.driveFilesTotalChart();
+				case 'network-requests': return this.networkRequestsChart();
+				case 'network-time': return this.networkTimeChart();
+				case 'network-usage': return this.networkUsageChart();
 			}
 		},
 
@@ -544,7 +555,95 @@ export default Vue.extend({
 					}
 				}
 			}];
-		}
+		},
+
+		networkRequestsChart(): any {
+			const data = this.stats.slice().reverse().map(x => ({
+				date: new Date(x.date),
+				requests: x.network.requests
+			}));
+
+			return [{
+				datasets: [{
+					label: 'Requests',
+					fill: true,
+					backgroundColor: rgba(colors.localPlus),
+					borderColor: colors.localPlus,
+					borderWidth: 2,
+					pointBackgroundColor: '#fff',
+					lineTension: 0,
+					data: data.map(x => ({ t: x.date, y: x.requests }))
+				}]
+			}];
+		},
+
+		networkTimeChart(): any {
+			const data = this.stats.slice().reverse().map(x => ({
+				date: new Date(x.date),
+				time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0,
+			}));
+
+			return [{
+				datasets: [{
+					label: 'Avg time (ms)',
+					fill: true,
+					backgroundColor: rgba(colors.localPlus),
+					borderColor: colors.localPlus,
+					borderWidth: 2,
+					pointBackgroundColor: '#fff',
+					lineTension: 0,
+					data: data.map(x => ({ t: x.date, y: x.time }))
+				}]
+			}];
+		},
+
+		networkUsageChart(): any {
+			const data = this.stats.slice().reverse().map(x => ({
+				date: new Date(x.date),
+				incoming: x.network.incomingBytes,
+				outgoing: x.network.outgoingBytes
+			}));
+
+			return [{
+				datasets: [{
+					label: 'Incoming',
+					fill: true,
+					backgroundColor: rgba(colors.incoming),
+					borderColor: colors.incoming,
+					borderWidth: 2,
+					pointBackgroundColor: '#fff',
+					lineTension: 0,
+					data: data.map(x => ({ t: x.date, y: x.incoming }))
+				}, {
+					label: 'Outgoing',
+					fill: true,
+					backgroundColor: rgba(colors.outgoing),
+					borderColor: colors.outgoing,
+					borderWidth: 2,
+					pointBackgroundColor: '#fff',
+					lineTension: 0,
+					data: data.map(x => ({ t: x.date, y: x.outgoing }))
+				}]
+			}, {
+				scales: {
+					yAxes: [{
+						ticks: {
+							callback: value => {
+								return Vue.filter('bytes')(value, 1);
+							}
+						}
+					}]
+				},
+				tooltips: {
+					callbacks: {
+						label: (tooltipItem, data) => {
+							const label = data.datasets[tooltipItem.datasetIndex].label || '';
+							return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`;
+						}
+					}
+				}
+			}];
+		},
 	}
 });
 </script>
diff --git a/src/client/app/desktop/views/components/context-menu.vue b/src/client/app/desktop/views/components/context-menu.vue
index afb6838eb6..49aeac143f 100644
--- a/src/client/app/desktop/views/components/context-menu.vue
+++ b/src/client/app/desktop/views/components/context-menu.vue
@@ -64,7 +64,7 @@ export default Vue.extend({
 			});
 
 			this.$emit('closed');
-			this.$destroy();
+			this.destroyDom();
 		}
 	}
 });
diff --git a/src/client/app/desktop/views/components/dialog.vue b/src/client/app/desktop/views/components/dialog.vue
index aff21c1754..bbb1e0030c 100644
--- a/src/client/app/desktop/views/components/dialog.vue
+++ b/src/client/app/desktop/views/components/dialog.vue
@@ -78,7 +78,7 @@ export default Vue.extend({
 				scale: 0.8,
 				duration: 300,
 				easing: [ 0.5, -0.5, 1, 0.5 ],
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		},
 		onBgClick() {
diff --git a/src/client/app/desktop/views/components/friends-maker.vue b/src/client/app/desktop/views/components/friends-maker.vue
index 7dfd9e4359..4e8a212b00 100644
--- a/src/client/app/desktop/views/components/friends-maker.vue
+++ b/src/client/app/desktop/views/components/friends-maker.vue
@@ -14,7 +14,7 @@
 	<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>
 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p>
 	<a class="refresh" @click="refresh">%i18n:@refresh%</a>
-	<button class="close" @click="$destroy()" title="%i18n:@close%">%fa:times%</button>
+	<button class="close" @click="destroyDom()" title="%i18n:@close%">%fa:times%</button>
 </div>
 </template>
 
diff --git a/src/client/app/desktop/views/components/media-image-dialog.vue b/src/client/app/desktop/views/components/media-image-dialog.vue
index 026522d907..89a340d3ae 100644
--- a/src/client/app/desktop/views/components/media-image-dialog.vue
+++ b/src/client/app/desktop/views/components/media-image-dialog.vue
@@ -26,7 +26,7 @@ export default Vue.extend({
 				opacity: 0,
 				duration: 100,
 				easing: 'linear',
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		}
 	}
diff --git a/src/client/app/desktop/views/components/media-image.vue b/src/client/app/desktop/views/components/media-image.vue
index 904dc7f832..3cff8cfc04 100644
--- a/src/client/app/desktop/views/components/media-image.vue
+++ b/src/client/app/desktop/views/components/media-image.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide" @click="hide = false">
+<div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide && !$store.state.device.alwaysShowNsfw" @click="hide = false">
 	<div>
 		<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
 		<span>%i18n:@click-to-show%</span>
diff --git a/src/client/app/desktop/views/components/media-video-dialog.vue b/src/client/app/desktop/views/components/media-video-dialog.vue
index 959cefa42c..03c93c8939 100644
--- a/src/client/app/desktop/views/components/media-video-dialog.vue
+++ b/src/client/app/desktop/views/components/media-video-dialog.vue
@@ -28,7 +28,7 @@ export default Vue.extend({
 				opacity: 0,
 				duration: 100,
 				easing: 'linear',
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		}
 	}
diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue
index 1ba4a9a447..7307eeb7dc 100644
--- a/src/client/app/desktop/views/components/note-detail.vue
+++ b/src/client/app/desktop/views/components/note-detail.vue
@@ -37,20 +37,26 @@
 			</router-link>
 		</header>
 		<div class="body">
-			<div class="text">
-				<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
-				<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
-				<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
-			</div>
-			<div class="media" v-if="p.media.length > 0">
-				<mk-media-list :media-list="p.media" :raw="true"/>
-			</div>
-			<mk-poll v-if="p.poll" :note="p"/>
-			<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
-			<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
-			<div class="map" v-if="p.geo" ref="map"></div>
-			<div class="renote" v-if="p.renote">
-				<mk-note-preview :note="p.renote"/>
+			<p v-if="p.cw != null" class="cw">
+				<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
+				<mk-cw-button v-model="showContent"/>
+			</p>
+			<div class="content" v-show="p.cw == null || showContent">
+				<div class="text">
+					<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
+					<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
+					<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
+				</div>
+				<div class="files" v-if="p.files.length > 0">
+					<mk-media-list :media-list="p.files" :raw="true"/>
+				</div>
+				<mk-poll v-if="p.poll" :note="p"/>
+				<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
+				<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
+				<div class="map" v-if="p.geo" ref="map"></div>
+				<div class="renote" v-if="p.renote">
+					<mk-note-preview :note="p.renote"/>
+				</div>
 			</div>
 		</div>
 		<footer>
@@ -86,6 +92,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
 import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 import XSub from './notes.note.sub.vue';
+import { sum } from '../../../../../prelude/array';
 
 export default Vue.extend({
 	components: {
@@ -104,6 +111,7 @@ export default Vue.extend({
 
 	data() {
 		return {
+			showContent: false,
 			conversation: [],
 			conversationFetching: false,
 			replies: []
@@ -114,22 +122,24 @@ export default Vue.extend({
 		isRenote(): boolean {
 			return (this.note.renote &&
 				this.note.text == null &&
-				this.note.mediaIds.length == 0 &&
+				this.note.fileIds.length == 0 &&
 				this.note.poll == null);
 		},
+
 		p(): any {
 			return this.isRenote ? this.note.renote : this.note;
 		},
+
 		reactionsCount(): number {
 			return this.p.reactionCounts
-				? Object.keys(this.p.reactionCounts)
-					.map(key => this.p.reactionCounts[key])
-					.reduce((a, b) => a + b)
+				? sum(Object.values(this.p.reactionCounts))
 				: 0;
 		},
+
 		title(): string {
 			return new Date(this.p.createdAt).toLocaleString();
 		},
+
 		urls(): string[] {
 			if (this.p.text) {
 				const ast = parse(this.p.text);
@@ -184,22 +194,26 @@ export default Vue.extend({
 				this.conversation = conversation.reverse();
 			});
 		},
+
 		reply() {
 			(this as any).os.new(MkPostFormWindow, {
 				reply: this.p
 			});
 		},
+
 		renote() {
 			(this as any).os.new(MkRenoteFormWindow, {
 				note: this.p
 			});
 		},
+
 		react() {
 			(this as any).os.new(MkReactionPicker, {
 				source: this.$refs.reactButton,
 				note: this.p
 			});
 		},
+
 		menu() {
 			(this as any).os.new(MkNoteMenu, {
 				source: this.$refs.menuButton,
@@ -327,37 +341,49 @@ root(isDark)
 		> .body
 			padding 8px 0
 
-			> .text
+			> .cw
 				cursor default
 				display block
 				margin 0
 				padding 0
 				overflow-wrap break-word
-				font-size 1.5em
 				color isDark ? #fff : #717171
 
-			> .renote
-				margin 8px 0
+				> .text
+					margin-right 8px
 
-				> .mk-note-preview
-					padding 16px
-					border dashed 1px #c0dac6
-					border-radius 8px
+			> .content
+				> .text
+					cursor default
+					display block
+					margin 0
+					padding 0
+					overflow-wrap break-word
+					font-size 1.5em
+					color isDark ? #fff : #717171
 
-			> .location
-				margin 4px 0
-				font-size 12px
-				color #ccc
+				> .renote
+					margin 8px 0
 
-			> .map
-				width 100%
-				height 300px
+					> *
+						padding 16px
+						border dashed 1px #c0dac6
+						border-radius 8px
 
-				&:empty
-					display none
+				> .location
+					margin 4px 0
+					font-size 12px
+					color #ccc
 
-			> .mk-url-preview
-				margin-top 8px
+				> .map
+					width 100%
+					height 300px
+
+					&:empty
+						display none
+
+				> .mk-url-preview
+					margin-top 8px
 
 		> footer
 			font-size 1.2em
diff --git a/src/client/app/desktop/views/components/note-preview.vue b/src/client/app/desktop/views/components/note-preview.vue
index c723db98c0..6c84165356 100644
--- a/src/client/app/desktop/views/components/note-preview.vue
+++ b/src/client/app/desktop/views/components/note-preview.vue
@@ -1,10 +1,16 @@
 <template>
-<div class="mk-note-preview" :title="title">
+<div class="qiziqtywpuaucsgarwajitwaakggnisj" :title="title">
 	<mk-avatar class="avatar" :user="note.user" v-if="!mini"/>
 	<div class="main">
 		<mk-note-header class="header" :note="note" :mini="true"/>
 		<div class="body">
-			<mk-sub-note-content class="text" :note="note"/>
+			<p v-if="note.cw != null" class="cw">
+				<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
+				<mk-cw-button v-model="showContent"/>
+			</p>
+			<div class="content" v-show="note.cw == null || showContent">
+				<mk-sub-note-content class="text" :note="note"/>
+			</div>
 		</div>
 	</div>
 </div>
@@ -25,6 +31,13 @@ export default Vue.extend({
 			default: false
 		}
 	},
+
+	data() {
+		return {
+			showContent: false
+		};
+	},
+
 	computed: {
 		title(): string {
 			return new Date(this.note.createdAt).toLocaleString();
@@ -52,16 +65,28 @@ root(isDark)
 
 		> .body
 
-			> .text
+			> .cw
 				cursor default
+				display block
 				margin 0
 				padding 0
-				color isDark ? #959ba7 : #717171
+				overflow-wrap break-word
+				color isDark ? #fff : #717171
 
-.mk-note-preview[data-darkmode]
+				> .text
+					margin-right 8px
+
+			> .content
+				> .text
+					cursor default
+					margin 0
+					padding 0
+					color isDark ? #959ba7 : #717171
+
+.qiziqtywpuaucsgarwajitwaakggnisj[data-darkmode]
 	root(true)
 
-.mk-note-preview:not([data-darkmode])
+.qiziqtywpuaucsgarwajitwaakggnisj:not([data-darkmode])
 	root(false)
 
 </style>
diff --git a/src/client/app/desktop/views/components/notes.note.sub.vue b/src/client/app/desktop/views/components/notes.note.sub.vue
index fc851e83e9..8f01ddd43c 100644
--- a/src/client/app/desktop/views/components/notes.note.sub.vue
+++ b/src/client/app/desktop/views/components/notes.note.sub.vue
@@ -1,10 +1,16 @@
 <template>
-<div class="sub" :title="title">
+<div class="tkfdzaxtkdeianobciwadajxzbddorql" :title="title">
 	<mk-avatar class="avatar" :user="note.user"/>
 	<div class="main">
 		<mk-note-header class="header" :note="note"/>
 		<div class="body">
-			<mk-sub-note-content class="text" :note="note"/>
+			<p v-if="note.cw != null" class="cw">
+				<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
+				<mk-cw-button v-model="showContent"/>
+			</p>
+			<div class="content" v-show="note.cw == null || showContent">
+				<mk-sub-note-content class="text" :note="note"/>
+			</div>
 		</div>
 	</div>
 </div>
@@ -14,7 +20,19 @@
 import Vue from 'vue';
 
 export default Vue.extend({
-	props: ['note'],
+	props: {
+		note: {
+			type: Object,
+			required: true
+		}
+	},
+
+	data() {
+		return {
+			showContent: false
+		};
+	},
+
 	computed: {
 		title(): string {
 			return new Date(this.note.createdAt).toLocaleString();
@@ -48,20 +66,32 @@ root(isDark)
 
 		> .body
 
-			> .text
+			> .cw
 				cursor default
+				display block
 				margin 0
 				padding 0
-				color isDark ? #959ba7 : #717171
+				overflow-wrap break-word
+				color isDark ? #fff : #717171
 
-				pre
-					max-height 120px
-					font-size 80%
+				> .text
+					margin-right 8px
 
-.sub[data-darkmode]
+			> .content
+				> .text
+					cursor default
+					margin 0
+					padding 0
+					color isDark ? #959ba7 : #717171
+
+					pre
+						max-height 120px
+						font-size 80%
+
+.tkfdzaxtkdeianobciwadajxzbddorql[data-darkmode]
 	root(true)
 
-.sub:not([data-darkmode])
+.tkfdzaxtkdeianobciwadajxzbddorql:not([data-darkmode])
 	root(false)
 
 </style>
diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue
index 7592ae3905..46a866f9a7 100644
--- a/src/client/app/desktop/views/components/notes.note.vue
+++ b/src/client/app/desktop/views/components/notes.note.vue
@@ -18,7 +18,7 @@
 			<div class="body">
 				<p v-if="p.cw != null" class="cw">
 					<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
-					<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@hide%' : '%i18n:@see-more%' }}</span>
+					<mk-cw-button v-model="showContent"/>
 				</p>
 				<div class="content" v-show="p.cw == null || showContent">
 					<div class="text">
@@ -28,15 +28,13 @@
 						<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
 						<a class="rp" v-if="p.renote">RP:</a>
 					</div>
-					<div class="media" v-if="p.media.length > 0">
-						<mk-media-list :media-list="p.media"/>
+					<div class="files" v-if="p.files.length > 0">
+						<mk-media-list :media-list="p.files"/>
 					</div>
 					<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
 					<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
 					<div class="map" v-if="p.geo" ref="map"></div>
-					<div class="renote" v-if="p.renote">
-						<mk-note-preview :note="p.renote"/>
-					</div>
+					<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
 					<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 				</div>
 			</div>
@@ -78,6 +76,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
 import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 import XSub from './notes.note.sub.vue';
+import { sum } from '../../../../../prelude/array';
 
 function focus(el, fn) {
 	const target = fn(el);
@@ -95,7 +94,12 @@ export default Vue.extend({
 		XSub
 	},
 
-	props: ['note'],
+	props: {
+		note: {
+			type: Object,
+			required: true
+		}
+	},
 
 	data() {
 		return {
@@ -110,7 +114,7 @@ export default Vue.extend({
 		isRenote(): boolean {
 			return (this.note.renote &&
 				this.note.text == null &&
-				this.note.mediaIds.length == 0 &&
+				this.note.fileIds.length == 0 &&
 				this.note.poll == null);
 		},
 
@@ -120,9 +124,7 @@ export default Vue.extend({
 
 		reactionsCount(): number {
 			return this.p.reactionCounts
-				? Object.keys(this.p.reactionCounts)
-					.map(key => this.p.reactionCounts[key])
-					.reduce((a, b) => a + b)
+				? sum(Object.values(this.p.reactionCounts))
 				: 0;
 		},
 
@@ -399,19 +401,6 @@ root(isDark)
 					> .text
 						margin-right 8px
 
-					> .toggle
-						display inline-block
-						padding 4px 8px
-						font-size 0.7em
-						color isDark ? #393f4f : #fff
-						background isDark ? #687390 : #b1b9c1
-						border-radius 2px
-						cursor pointer
-						user-select none
-
-						&:hover
-							background isDark ? #707b97 : #bbc4ce
-
 				> .content
 
 					> .text
@@ -470,7 +459,7 @@ root(isDark)
 					> .renote
 						margin 8px 0
 
-						> .mk-note-preview
+						> *
 							padding 16px
 							border dashed 1px isDark ? #4e945e : #c0dac6
 							border-radius 8px
diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue
index a1c1207a7b..26aa0c8dea 100644
--- a/src/client/app/desktop/views/components/notes.vue
+++ b/src/client/app/desktop/views/components/notes.vue
@@ -10,8 +10,7 @@
 	</div>
 
 	<!-- トランジションを有効にするとなぜかメモリリークする -->
-	<!--<transition-group name="mk-notes" class="transition">-->
-	<div class="notes">
+	<transition-group name="mk-notes" class="notes transition" tag="div">
 		<template v-for="(note, i) in _notes">
 			<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
 			<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
@@ -19,8 +18,7 @@
 				<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
 			</p>
 		</template>
-	</div>
-	<!--</transition-group>-->
+	</transition-group>
 
 	<footer v-if="more">
 		<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
@@ -122,7 +120,7 @@ export default Vue.extend({
 		prepend(note, silent = false) {
 			//#region 弾く
 			const isMyNote = note.userId == this.$store.state.i.id;
-			const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
+			const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
 
 			if (this.$store.state.settings.showMyRenotes === false) {
 				if (isMyNote && isPureRenote) {
diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue
index bfe71903e4..9d0e73adef 100644
--- a/src/client/app/desktop/views/components/notifications.vue
+++ b/src/client/app/desktop/views/components/notifications.vue
@@ -2,8 +2,7 @@
 <div class="mk-notifications">
 	<div class="notifications" v-if="notifications.length != 0">
 		<!-- トランジションを有効にするとなぜかメモリリークする -->
-		<!-- <transition-group name="mk-notifications" class="transition"> -->
-		<div>
+		<transition-group name="mk-notifications" class="transition" tag="div">
 			<template v-for="(notification, i) in _notifications">
 				<div class="notification" :class="notification.type" :key="notification.id">
 					<mk-time :time="notification.createdAt"/>
@@ -97,8 +96,7 @@
 					<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
 				</p>
 			</template>
-		</div>
-		<!-- </transition-group> -->
+		</transition-group>
 	</div>
 	<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
 		<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
diff --git a/src/client/app/desktop/views/components/post-form-window.vue b/src/client/app/desktop/views/components/post-form-window.vue
index 51a416e281..a88c96d1bf 100644
--- a/src/client/app/desktop/views/components/post-form-window.vue
+++ b/src/client/app/desktop/views/components/post-form-window.vue
@@ -4,7 +4,7 @@
 		<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
 		<span v-if="!reply">%i18n:@note%</span>
 		<span v-if="reply">%i18n:@reply%</span>
-		<span class="count" v-if="media.length != 0">{{ '%i18n:@attaches%'.replace('{}', media.length) }}</span>
+		<span class="count" v-if="files.length != 0">{{ '%i18n:@attaches%'.replace('{}', files.length) }}</span>
 		<span class="count" v-if="uploadings.length != 0">{{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
 	</span>
 
@@ -14,7 +14,7 @@
 			:reply="reply"
 			@posted="onPosted"
 			@change-uploadings="onChangeUploadings"
-			@change-attached-media="onChangeMedia"
+			@change-attached-files="onChangeFiles"
 			@geo-attached="onGeoAttached"
 			@geo-dettached="onGeoDettached"/>
 	</div>
@@ -29,7 +29,7 @@ export default Vue.extend({
 	data() {
 		return {
 			uploadings: [],
-			media: [],
+			files: [],
 			geo: null
 		};
 	},
@@ -42,8 +42,8 @@ export default Vue.extend({
 		onChangeUploadings(files) {
 			this.uploadings = files;
 		},
-		onChangeMedia(media) {
-			this.media = media;
+		onChangeFiles(files) {
+			this.files = files;
 		},
 		onGeoAttached(geo) {
 			this.geo = geo;
diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue
index 2ca5484610..c371940aa3 100644
--- a/src/client/app/desktop/views/components/post-form.vue
+++ b/src/client/app/desktop/views/components/post-form.vue
@@ -20,7 +20,7 @@
 			@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
 			v-autocomplete="'text'"
 		></textarea>
-		<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
+		<div class="files" :class="{ with: poll }" v-show="files.length != 0">
 			<x-draggable :list="files" :options="{ animation: 150 }">
 				<div v-for="file in files" :key="file.id">
 					<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
@@ -45,7 +45,7 @@
 		<span v-if="visibility === 'specified'">%fa:envelope%</span>
 		<span v-if="visibility === 'private'">%fa:lock%</span>
 	</button>
-	<p class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</p>
+	<p class="text-count" :class="{ over: this.trimmedLength(text) > 1000 }">{{ 1000 - this.trimmedLength(text) }}</p>
 	<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
 		{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
 	</button>
@@ -62,6 +62,9 @@ import getFace from '../../../common/scripts/get-face';
 import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
 import parse from '../../../../../mfm/parse';
 import { host } from '../../../config';
+import { erase, unique } from '../../../../../prelude/array';
+import { length } from 'stringz';
+import parseAcct from '../../../../../misc/acct/parse';
 
 export default Vue.extend({
 	components: {
@@ -99,7 +102,7 @@ export default Vue.extend({
 			useCw: false,
 			cw: null,
 			geo: null,
-			visibility: this.$store.state.device.visibility || 'public',
+			visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
 			visibleUsers: [],
 			autocomplete: null,
 			draghover: false,
@@ -145,7 +148,7 @@ export default Vue.extend({
 		canPost(): boolean {
 			return !this.posting &&
 				(1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) &&
-				(this.text.trim().length <= 1000);
+				(length(this.text.trim()) <= 1000);
 		}
 	},
 
@@ -188,7 +191,7 @@ export default Vue.extend({
 							(this.$refs.poll as any).set(draft.data.poll);
 						});
 					}
-					this.$emit('change-attached-media', this.files);
+					this.$emit('change-attached-files', this.files);
 				}
 			}
 
@@ -197,6 +200,10 @@ export default Vue.extend({
 	},
 
 	methods: {
+	  trimmedLength(text: string) {
+			return length(text.trim());
+		},
+
 		addTag(tag: string) {
 			insertTextAtCursor(this.$refs.text, ` #${tag} `);
 		},
@@ -225,12 +232,12 @@ export default Vue.extend({
 
 		attachMedia(driveFile) {
 			this.files.push(driveFile);
-			this.$emit('change-attached-media', this.files);
+			this.$emit('change-attached-files', this.files);
 		},
 
 		detachMedia(id) {
 			this.files = this.files.filter(x => x.id != id);
-			this.$emit('change-attached-media', this.files);
+			this.$emit('change-attached-files', this.files);
 		},
 
 		onChangeFile() {
@@ -249,7 +256,7 @@ export default Vue.extend({
 			this.text = '';
 			this.files = [];
 			this.poll = false;
-			this.$emit('change-attached-media', this.files);
+			this.$emit('change-attached-files', this.files);
 		},
 
 		onKeydown(e) {
@@ -297,7 +304,7 @@ export default Vue.extend({
 			if (driveFile != null && driveFile != '') {
 				const file = JSON.parse(driveFile);
 				this.files.push(file);
-				this.$emit('change-attached-media', this.files);
+				this.$emit('change-attached-files', this.files);
 				e.preventDefault();
 			}
 			//#endregion
@@ -336,17 +343,16 @@ export default Vue.extend({
 		addVisibleUser() {
 			(this as any).apis.input({
 				title: '%i18n:@enter-username%'
-			}).then(username => {
-				(this as any).api('users/show', {
-					username
-				}).then(user => {
+			}).then(acct => {
+				if (acct.startsWith('@')) acct = acct.substr(1);
+				(this as any).api('users/show', parseAcct(acct)).then(user => {
 					this.visibleUsers.push(user);
 				});
 			});
 		},
 
 		removeVisibleUser(user) {
-			this.visibleUsers = this.visibleUsers.filter(u => u != user);
+			this.visibleUsers = erase(user, this.visibleUsers);
 		},
 
 		post() {
@@ -354,7 +360,7 @@ export default Vue.extend({
 
 			(this as any).api('notes/create', {
 				text: this.text == '' ? undefined : this.text,
-				mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
+				fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
 				replyId: this.reply ? this.reply.id : undefined,
 				renoteId: this.renote ? this.renote.id : undefined,
 				poll: this.poll ? (this.$refs.poll as any).get() : undefined,
@@ -391,7 +397,7 @@ export default Vue.extend({
 			if (this.text && this.text != '') {
 				const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
-				localStorage.setItem('hashtags', JSON.stringify(hashtags.concat(history).reduce((a, c) => a.includes(c) ? a : [...a, c], [])));
+				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
 			}
 		},
 
@@ -514,7 +520,7 @@ root(isDark)
 				margin-right 8px
 				white-space nowrap
 
-		> .medias
+		> .files
 			margin 0
 			padding 0
 			background isDark ? #181b23 : lighten($theme-color, 98%)
diff --git a/src/client/app/desktop/views/components/renote-form.vue b/src/client/app/desktop/views/components/renote-form.vue
index 38eab3362f..c5192ecaac 100644
--- a/src/client/app/desktop/views/components/renote-form.vue
+++ b/src/client/app/desktop/views/components/renote-form.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="mk-renote-form">
-	<mk-note-preview :note="note"/>
+	<mk-note-preview class="preview" :note="note"/>
 	<template v-if="!quote">
 		<footer>
 			<a class="quote" v-if="!quote" @click="onQuote">%i18n:@quote%</a>
@@ -61,7 +61,7 @@ export default Vue.extend({
 
 root(isDark)
 
-	> .mk-note-preview
+	> .preview
 		margin 16px 22px
 
 	> footer
diff --git a/src/client/app/desktop/views/components/settings.drive.vue b/src/client/app/desktop/views/components/settings.drive.vue
index e8a3cc9685..d254b27110 100644
--- a/src/client/app/desktop/views/components/settings.drive.vue
+++ b/src/client/app/desktop/views/components/settings.drive.vue
@@ -1,7 +1,6 @@
 <template>
 <div class="root">
 	<template v-if="!fetching">
-		<el-progress :text-inside="true" :stroke-width="18" :percentage="Math.floor((usage / capacity) * 100)"/>
 		<p><b>{{ capacity | bytes }}</b>%i18n:max%<b>{{ usage | bytes }}</b>%i18n:in-use%</p>
 	</template>
 </div>
diff --git a/src/client/app/desktop/views/components/settings.profile.vue b/src/client/app/desktop/views/components/settings.profile.vue
index 262583b640..d47b5b224b 100644
--- a/src/client/app/desktop/views/components/settings.profile.vue
+++ b/src/client/app/desktop/views/components/settings.profile.vue
@@ -19,7 +19,7 @@
 	</label>
 	<label class="ui from group">
 		<p>%i18n:@birthday%</p>
-		<el-date-picker v-model="birthday" type="date" value-format="yyyy-MM-dd"/>
+		<input type="date" v-model="birthday"/>
 	</label>
 	<button class="ui primary" @click="save">%i18n:@save%</button>
 	<section>
@@ -30,6 +30,7 @@
 		<h2>%i18n:@other%</h2>
 		<mk-switch v-model="$store.state.i.isBot" @change="onChangeIsBot" text="%i18n:@is-bot%"/>
 		<mk-switch v-model="$store.state.i.isCat" @change="onChangeIsCat" text="%i18n:@is-cat%"/>
+		<mk-switch v-model="alwaysMarkNsfw" text="%i18n:common.always-mark-nsfw%"/>
 	</section>
 </div>
 </template>
@@ -46,6 +47,12 @@ export default Vue.extend({
 			birthday: null,
 		};
 	},
+	computed: {
+		alwaysMarkNsfw: {
+			get() { return this.$store.state.i.settings.alwaysMarkNsfw; },
+			set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); }
+		},
+	},
 	created() {
 		this.name = this.$store.state.i.name || '';
 		this.location = this.$store.state.i.profile.location;
diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue
index 7d6f1d55fb..709715e598 100644
--- a/src/client/app/desktop/views/components/settings.vue
+++ b/src/client/app/desktop/views/components/settings.vue
@@ -20,12 +20,28 @@
 
 		<section class="web" v-show="page == 'web'">
 			<h1>%i18n:@behaviour%</h1>
-			<mk-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll" text="%i18n:@fetch-on-scroll%">
+			<mk-switch v-model="fetchOnScroll" text="%i18n:@fetch-on-scroll%">
 				<span>%i18n:@fetch-on-scroll-desc%</span>
 			</mk-switch>
 			<mk-switch v-model="autoPopout" text="%i18n:@auto-popout%">
 				<span>%i18n:@auto-popout-desc%</span>
 			</mk-switch>
+
+			<section>
+				<header>%i18n:@note-visibility%</header>
+				<mk-switch v-model="rememberNoteVisibility" text="%i18n:@remember-note-visibility%"/>
+				<section>
+					<header>%i18n:@default-note-visibility%</header>
+					<ui-select v-model="defaultNoteVisibility">
+						<option value="public">%i18n:common.note-visibility.public%</option>
+						<option value="home">%i18n:common.note-visibility.home%</option>
+						<option value="followers">%i18n:common.note-visibility.followers%</option>
+						<option value="specified">%i18n:common.note-visibility.specified%</option>
+						<option value="private">%i18n:common.note-visibility.private%</option>
+					</ui-select>
+				</section>
+			</section>
+
 			<details>
 				<summary>%i18n:@advanced%</summary>
 				<mk-switch v-model="apiViaStream" text="%i18n:@api-via-stream%">
@@ -43,23 +59,26 @@
 				<button class="ui" @click="updateWallpaper">%i18n:@choose-wallpaper%</button>
 				<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
 				<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
-				<mk-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
-				<mk-switch v-model="$store.state.settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
-				<mk-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi" text="%i18n:common.i-like-sushi%"/>
+				<mk-switch v-model="circleIcons" text="%i18n:@circle-icons%"/>
+				<mk-switch v-model="contrastedAcct" text="%i18n:@contrasted-acct%"/>
+				<mk-switch v-model="showFullAcct" text="%i18n:common.show-full-acct%"/>
+				<mk-switch v-model="gradientWindowHeader" text="%i18n:@gradient-window-header%"/>
+				<mk-switch v-model="iLikeSushi" text="%i18n:common.i-like-sushi%"/>
 			</div>
-			<mk-switch v-model="$store.state.settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
-			<mk-switch v-model="$store.state.settings.suggestRecentHashtags" @change="onChangeSuggestRecentHashtags" text="%i18n:@suggest-recent-hashtags%"/>
-			<mk-switch v-model="$store.state.settings.showClockOnHeader" @change="onChangeShowClockOnHeader" text="%i18n:@show-clock-on-header%"/>
-			<mk-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget" text="%i18n:@show-reply-target%"/>
-			<mk-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes" text="%i18n:@show-my-renotes%"/>
-			<mk-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/>
-			<mk-switch v-model="$store.state.settings.showLocalRenotes" @change="onChangeShowLocalRenotes" text="%i18n:@show-local-renotes%"/>
-			<mk-switch v-model="$store.state.settings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%">
+			<mk-switch v-model="showPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
+			<mk-switch v-model="suggestRecentHashtags" text="%i18n:@suggest-recent-hashtags%"/>
+			<mk-switch v-model="showClockOnHeader" text="%i18n:@show-clock-on-header%"/>
+			<mk-switch v-model="alwaysShowNsfw" text="%i18n:common.always-show-nsfw%"/>
+			<mk-switch v-model="showReplyTarget" text="%i18n:@show-reply-target%"/>
+			<mk-switch v-model="showMyRenotes" text="%i18n:@show-my-renotes%"/>
+			<mk-switch v-model="showRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/>
+			<mk-switch v-model="showLocalRenotes" text="%i18n:@show-local-renotes%"/>
+			<mk-switch v-model="showMaps" text="%i18n:@show-maps%">
 				<span>%i18n:@show-maps-desc%</span>
 			</mk-switch>
-			<mk-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm" text="%i18n:common.disable-animated-mfm%"/>
-			<mk-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels" text="%i18n:common.show-reversi-board-labels%"/>
-			<mk-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones" text="%i18n:common.use-contrast-reversi-stones%"/>
+			<mk-switch v-model="disableAnimatedMfm" text="%i18n:common.disable-animated-mfm%"/>
+			<mk-switch v-model="games_reversi_showBoardLabels" text="%i18n:common.show-reversi-board-labels%"/>
+			<mk-switch v-model="games_reversi_useContrastStones" text="%i18n:common.use-contrast-reversi-stones%"/>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
@@ -68,32 +87,31 @@
 				<span>%i18n:@enable-sounds-desc%</span>
 			</mk-switch>
 			<label>%i18n:@volume%</label>
-			<el-slider
+			<input type="range"
 				v-model="soundVolume"
-				:show-input="true"
-				:format-tooltip="v => `${v * 100}%`"
 				:disabled="!enableSounds"
-				:max="1"
-				:step="0.1"
+				max="1"
+				step="0.1"
 			/>
 			<button class="ui button" @click="soundTest">%fa:volume-up% %i18n:@test%</button>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
 			<h1>%i18n:@mobile%</h1>
-			<mk-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile" text="%i18n:@disable-via-mobile%"/>
+			<mk-switch v-model="disableViaMobile" text="%i18n:@disable-via-mobile%"/>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
 			<h1>%i18n:@language%</h1>
-			<el-select v-model="lang" placeholder="%i18n:@pick-language%">
-				<el-option-group label="%i18n:@recommended%">
-					<el-option label="%i18n:@auto%" :value="null"/>
-				</el-option-group>
-				<el-option-group label="%i18n:@specify-language%">
-					<el-option v-for="x in langs" :label="x[1]" :value="x[0]" :key="x[0]"/>
-				</el-option-group>
-			</el-select>
+			<select v-model="lang" placeholder="%i18n:@pick-language%">
+				<optgroup label="%i18n:@recommended%">
+					<option value="">%i18n:@auto%</option>
+				</optgroup>
+
+				<optgroup label="%i18n:@specify-language%">
+					<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
+				</optgroup>
+			</select>
 			<div class="none ui info">
 				<p>%fa:info-circle%%i18n:@language-desc%</p>
 			</div>
@@ -188,10 +206,6 @@
 			<mk-switch v-model="enableExperimentalFeatures" text="%i18n:@experimental%">
 				<span>%i18n:@experimental-desc%</span>
 			</mk-switch>
-			<details v-if="debug">
-				<summary>%i18n:@tools%</summary>
-				<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
-			</details>
 		</section>
 	</div>
 </div>
@@ -209,7 +223,6 @@ import XSignins from './settings.signins.vue';
 import XDrive from './settings.drive.vue';
 import { url, langs, version } from '../../../config';
 import checkForUpdate from '../../../common/scripts/check-for-update';
-import MkTaskManager from './taskmanager.vue';
 
 export default Vue.extend({
 	components: {
@@ -276,7 +289,112 @@ export default Vue.extend({
 		enableExperimentalFeatures: {
 			get() { return this.$store.state.device.enableExperimentalFeatures; },
 			set(value) { this.$store.commit('device/set', { key: 'enableExperimentalFeatures', value }); }
-		}
+		},
+
+		alwaysShowNsfw: {
+			get() { return this.$store.state.device.alwaysShowNsfw; },
+			set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); }
+		},
+
+		fetchOnScroll: {
+			get() { return this.$store.state.settings.fetchOnScroll; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); }
+		},
+
+		rememberNoteVisibility: {
+			get() { return this.$store.state.settings.rememberNoteVisibility; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); }
+		},
+
+		defaultNoteVisibility: {
+			get() { return this.$store.state.settings.defaultNoteVisibility; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); }
+		},
+
+		showReplyTarget: {
+			get() { return this.$store.state.settings.showReplyTarget; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showReplyTarget', value }); }
+		},
+
+		showMyRenotes: {
+			get() { return this.$store.state.settings.showMyRenotes; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showMyRenotes', value }); }
+		},
+
+		showRenotedMyNotes: {
+			get() { return this.$store.state.settings.showRenotedMyNotes; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showRenotedMyNotes', value }); }
+		},
+
+		showLocalRenotes: {
+			get() { return this.$store.state.settings.showLocalRenotes; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showLocalRenotes', value }); }
+		},
+
+		showPostFormOnTopOfTl: {
+			get() { return this.$store.state.settings.showPostFormOnTopOfTl; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showPostFormOnTopOfTl', value }); }
+		},
+
+		suggestRecentHashtags: {
+			get() { return this.$store.state.settings.suggestRecentHashtags; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'suggestRecentHashtags', value }); }
+		},
+
+		showClockOnHeader: {
+			get() { return this.$store.state.settings.showClockOnHeader; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showClockOnHeader', value }); }
+		},
+
+		showMaps: {
+			get() { return this.$store.state.settings.showMaps; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showMaps', value }); }
+		},
+
+		circleIcons: {
+			get() { return this.$store.state.settings.circleIcons; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'circleIcons', value }); }
+		},
+
+		contrastedAcct: {
+			get() { return this.$store.state.settings.contrastedAcct; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'contrastedAcct', value }); }
+		},
+
+		showFullAcct: {
+			get() { return this.$store.state.settings.showFullAcct; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showFullAcct', value }); }
+		},
+
+		iLikeSushi: {
+			get() { return this.$store.state.settings.iLikeSushi; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); }
+		},
+
+		games_reversi_showBoardLabels: {
+			get() { return this.$store.state.settings.games.reversi.showBoardLabels; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.showBoardLabels', value }); }
+		},
+
+		games_reversi_useContrastStones: {
+			get() { return this.$store.state.settings.games.reversi.useContrastStones; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useContrastStones', value }); }
+		},
+
+		disableAnimatedMfm: {
+			get() { return this.$store.state.settings.disableAnimatedMfm; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'disableAnimatedMfm', value }); }
+		},
+
+		disableViaMobile: {
+			get() { return this.$store.state.settings.disableViaMobile; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
+		},
+
+		gradientWindowHeader: {
+			get() { return this.$store.state.settings.gradientWindowHeader; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'gradientWindowHeader', value }); }
+		},
 	},
 	created() {
 		(this as any).os.getMeta().then(meta => {
@@ -284,9 +402,6 @@ export default Vue.extend({
 		});
 	},
 	methods: {
-		taskmngr() {
-			(this as any).os.new(MkTaskManager);
-		},
 		customizeHome() {
 			this.$router.push('/i/customize-home');
 			this.$emit('done');
@@ -305,113 +420,11 @@ export default Vue.extend({
 				wallpaperId: null
 			});
 		},
-		onChangeFetchOnScroll(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'fetchOnScroll',
-				value: v
-			});
-		},
 		onChangeAutoWatch(v) {
 			(this as any).api('i/update', {
 				autoWatch: v
 			});
 		},
-		onChangeDark(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'dark',
-				value: v
-			});
-		},
-		onChangeShowPostFormOnTopOfTl(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showPostFormOnTopOfTl',
-				value: v
-			});
-		},
-		onChangeSuggestRecentHashtags(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'suggestRecentHashtags',
-				value: v
-			});
-		},
-		onChangeShowClockOnHeader(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showClockOnHeader',
-				value: v
-			});
-		},
-		onChangeShowReplyTarget(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showReplyTarget',
-				value: v
-			});
-		},
-		onChangeShowMyRenotes(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showMyRenotes',
-				value: v
-			});
-		},
-		onChangeShowRenotedMyNotes(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showRenotedMyNotes',
-				value: v
-			});
-		},
-		onChangeShowLocalRenotes(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showLocalRenotes',
-				value: v
-			});
-		},
-		onChangeShowMaps(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showMaps',
-				value: v
-			});
-		},
-		onChangeCircleIcons(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'circleIcons',
-				value: v
-			});
-		},
-		onChangeILikeSushi(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'iLikeSushi',
-				value: v
-			});
-		},
-		onChangeReversiBoardLabels(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'games.reversi.showBoardLabels',
-				value: v
-			});
-		},
-		onChangeUseContrastReversiStones(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'games.reversi.useContrastStones',
-				value: v
-			});
-		},
-		onChangeDisableAnimatedMfm(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'disableAnimatedMfm',
-				value: v
-			});
-		},
-		onChangeGradientWindowHeader(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'gradientWindowHeader',
-				value: v
-			});
-		},
-		onChangeDisableViaMobile(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'disableViaMobile',
-				value: v
-			});
-		},
 		checkForUpdate() {
 			this.checkingForUpdate = true;
 			checkForUpdate((this as any).os, true, true).then(newer => {
diff --git a/src/client/app/desktop/views/components/sub-note-content.vue b/src/client/app/desktop/views/components/sub-note-content.vue
index cb0374b910..6889dc231e 100644
--- a/src/client/app/desktop/views/components/sub-note-content.vue
+++ b/src/client/app/desktop/views/components/sub-note-content.vue
@@ -7,9 +7,9 @@
 		<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
 		<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a>
 	</div>
-	<details v-if="note.media.length > 0">
-		<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary>
-		<mk-media-list :media-list="note.media"/>
+	<details v-if="note.files.length > 0">
+		<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>
+		<mk-media-list :media-list="note.files"/>
 	</details>
 	<details v-if="note.poll">
 		<summary>%i18n:@poll%</summary>
diff --git a/src/client/app/desktop/views/components/taskmanager.vue b/src/client/app/desktop/views/components/taskmanager.vue
deleted file mode 100644
index 1f1385add8..0000000000
--- a/src/client/app/desktop/views/components/taskmanager.vue
+++ /dev/null
@@ -1,219 +0,0 @@
-<template>
-<mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
-	<span slot="header" :class="$style.header">%fa:stethoscope%%i18n:@title%</span>
-	<el-tabs :class="$style.content">
-		<el-tab-pane label="Requests">
-			<el-table
-				:data="os.requests"
-				style="width: 100%"
-				:default-sort="{prop: 'date', order: 'descending'}"
-			>
-				<el-table-column type="expand">
-					<template slot-scope="props">
-						<pre>{{ props.row.data }}</pre>
-						<pre>{{ props.row.res }}</pre>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					label="Requested at"
-					prop="date"
-					sortable
-				>
-					<template slot-scope="scope">
-						<b style="margin-right: 8px">{{ scope.row.date.getTime() }}</b>
-						<span>(<mk-time :time="scope.row.date"/>)</span>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					label="Name"
-				>
-					<template slot-scope="scope">
-						<b>{{ scope.row.name }}</b>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					label="Status"
-				>
-					<template slot-scope="scope">
-						<span>{{ scope.row.status || '(pending)' }}</span>
-					</template>
-				</el-table-column>
-			</el-table>
-		</el-tab-pane>
-
-		<el-tab-pane label="Streams">
-			<el-table
-				:data="os.connections"
-				style="width: 100%"
-			>
-				<el-table-column
-					label="Uptime"
-				>
-					<template slot-scope="scope">
-						<mk-timer v-if="scope.row.connectedAt" :time="scope.row.connectedAt"/>
-						<span v-else>-</span>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					label="Name"
-				>
-					<template slot-scope="scope">
-						<b>{{ scope.row.name == '' ? '[Home]' : scope.row.name }}</b>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					label="User"
-				>
-					<template slot-scope="scope">
-						<span>{{ scope.row.user || '(anonymous)' }}</span>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					prop="state"
-					label="State"
-				/>
-
-				<el-table-column
-					prop="in"
-					label="In"
-				/>
-
-				<el-table-column
-					prop="out"
-					label="Out"
-				/>
-			</el-table>
-		</el-tab-pane>
-
-		<el-tab-pane label="Streams (Inspect)">
-			<el-tabs type="card" style="height:50%">
-				<el-tab-pane v-for="c in os.connections" :label="c.name == '' ? '[Home]' : c.name" :key="c.id" :name="c.id" ref="connectionsTab">
-					<div style="padding: 12px 0 0 12px">
-					<el-button size="mini" @click="send(c)">Send</el-button>
-					<el-button size="mini" type="warning" @click="c.isSuspended = true" v-if="!c.isSuspended">Suspend</el-button>
-					<el-button size="mini" type="success" @click="c.isSuspended = false" v-else>Resume</el-button>
-					<el-button size="mini" type="danger" @click="c.close">Disconnect</el-button>
-				</div>
-
-					<el-table
-						:data="c.inout"
-						style="width: 100%"
-						:default-sort="{prop: 'at', order: 'descending'}"
-					>
-						<el-table-column type="expand">
-							<template slot-scope="props">
-								<pre>{{ props.row.data }}</pre>
-							</template>
-						</el-table-column>
-
-						<el-table-column
-							label="Date"
-							prop="at"
-							sortable
-						>
-							<template slot-scope="scope">
-								<b style="margin-right: 8px">{{ scope.row.at.getTime() }}</b>
-								<span>(<mk-time :time="scope.row.at"/>)</span>
-							</template>
-						</el-table-column>
-
-						<el-table-column
-							label="Type"
-						>
-							<template slot-scope="scope">
-								<span>{{ getMessageType(scope.row.data) }}</span>
-							</template>
-						</el-table-column>
-
-						<el-table-column
-							label="Incoming / Outgoing"
-							prop="type"
-						/>
-					</el-table>
-				</el-tab-pane>
-			</el-tabs>
-		</el-tab-pane>
-
-		<el-tab-pane label="Windows">
-			<el-table
-				:data="Array.from(os.windows.windows)"
-				style="width: 100%"
-			>
-				<el-table-column
-					label="Name"
-				>
-					<template slot-scope="scope">
-						<b>{{ scope.row.name || '(unknown)' }}</b>
-					</template>
-				</el-table-column>
-
-				<el-table-column
-					label="Operations"
-				>
-					<template slot-scope="scope">
-						<el-button size="mini" type="danger" @click="scope.row.close">Close</el-button>
-					</template>
-				</el-table-column>
-			</el-table>
-		</el-tab-pane>
-	</el-tabs>
-</mk-window>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
-	mounted() {
-		(this as any).os.windows.on('added', this.onWindowsChanged);
-		(this as any).os.windows.on('removed', this.onWindowsChanged);
-	},
-	beforeDestroy() {
-		(this as any).os.windows.off('added', this.onWindowsChanged);
-		(this as any).os.windows.off('removed', this.onWindowsChanged);
-	},
-	methods: {
-		getMessageType(data): string {
-			return data.type ? data.type : '-';
-		},
-		onWindowsChanged() {
-			this.$forceUpdate();
-		},
-		send(c) {
-			(this as any).apis.input({
-				title: 'Send a JSON message',
-				allowEmpty: false
-			}).then(json => {
-				c.send(JSON.parse(json));
-			});
-		}
-	}
-});
-</script>
-
-<style lang="stylus" module>
-.header
-	> [data-fa]
-		margin-right 4px
-
-.content
-	height 100%
-	overflow auto
-
-</style>
-
-<style>
-.el-tabs__header {
-	margin-bottom: 0 !important;
-}
-
-.el-tabs__item {
-	padding: 0 20px !important;
-}
-</style>
diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue
index 52a7753438..8d72016f22 100644
--- a/src/client/app/desktop/views/components/timeline.vue
+++ b/src/client/app/desktop/views/components/timeline.vue
@@ -2,8 +2,8 @@
 <div class="mk-timeline">
 	<header>
 		<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
-		<span :data-active="src == 'local'" @click="src = 'local'">%fa:R comments% %i18n:@local%</span>
-		<span :data-active="src == 'hybrid'" @click="src = 'hybrid'">%fa:share-alt% %i18n:@hybrid%</span>
+		<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
+		<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
 		<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
 		<span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span>
 		<button @click="chooseList" title="%i18n:@list%">%fa:list%</button>
@@ -29,7 +29,8 @@ export default Vue.extend({
 	data() {
 		return {
 			src: 'home',
-			list: null
+			list: null,
+			enableLocalTimeline: false
 		};
 	},
 
@@ -44,6 +45,10 @@ export default Vue.extend({
 	},
 
 	created() {
+		(this as any).os.getMeta().then(meta => {
+			this.enableLocalTimeline = !meta.disableLocalTimeline;
+		});
+
 		if (this.$store.state.device.tl) {
 			this.src = this.$store.state.device.tl.src;
 			if (this.src == 'list') {
diff --git a/src/client/app/desktop/views/components/ui-notification.vue b/src/client/app/desktop/views/components/ui-notification.vue
index 68413914c0..7519124870 100644
--- a/src/client/app/desktop/views/components/ui-notification.vue
+++ b/src/client/app/desktop/views/components/ui-notification.vue
@@ -27,7 +27,7 @@ export default Vue.extend({
 					translateY: -64,
 					duration: 500,
 					easing: 'easeInElastic',
-					complete: () => this.$destroy()
+					complete: () => this.destroyDom()
 				});
 			}, 6000);
 		});
diff --git a/src/client/app/desktop/views/components/user-preview.vue b/src/client/app/desktop/views/components/user-preview.vue
index 1e1755ec3c..f6d6d68a7f 100644
--- a/src/client/app/desktop/views/components/user-preview.vue
+++ b/src/client/app/desktop/views/components/user-preview.vue
@@ -75,7 +75,7 @@ export default Vue.extend({
 				'margin-top': '-8px',
 				duration: 200,
 				easing: 'easeOutQuad',
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		}
 	}
diff --git a/src/client/app/desktop/views/components/users-list.item.vue b/src/client/app/desktop/views/components/users-list.item.vue
index 262fd38cd1..f42d577fce 100644
--- a/src/client/app/desktop/views/components/users-list.item.vue
+++ b/src/client/app/desktop/views/components/users-list.item.vue
@@ -1,17 +1,16 @@
 <template>
-<div class="root item">
-	<mk-avatar class="avatar" :user="user"/>
-	<div class="main">
-		<header>
-			<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link>
-			<span class="username">@{{ user | acct }}</span>
-		</header>
-		<div class="body">
-			<p class="followed" v-if="user.isFollowed">%i18n:@followed%</p>
-			<div class="description">{{ user.description }}</div>
+<div class="zvdbznxvfixtmujpsigoccczftvpiwqh">
+	<div class="banner" :style="bannerStyle"></div>
+	<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
+	<div class="body">
+		<router-link :to="user | userPage" class="name">{{ user | userName }}</router-link>
+		<span class="username">@{{ user | acct }}</span>
+		<div class="description">
+			<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
 		</div>
+		<p class="followed" v-if="user.isFollowed">%i18n:@followed%</p>
+		<mk-follow-button :user="user" :size="'big'"/>
 	</div>
-	<mk-follow-button :user="user"/>
 </div>
 </template>
 
@@ -19,76 +18,69 @@
 import Vue from 'vue';
 
 export default Vue.extend({
-	props: ['user']
+	props: ['user'],
+
+	computed: {
+		bannerStyle(): any {
+			if (this.user.bannerUrl == null) return {};
+			return {
+				backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
+				backgroundImage: `url(${ this.user.bannerUrl })`
+			};
+		}
+	},
 });
 </script>
 
 <style lang="stylus" scoped>
-.root.item
-	padding 16px
-	font-size 16px
+.zvdbznxvfixtmujpsigoccczftvpiwqh
+	$bg = #fff
 
-	&:after
-		content ""
-		display block
-		clear both
+	margin 16px auto
+	max-width calc(100% - 32px)
+	font-size 16px
+	text-align center
+	background $bg
+	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
+
+	> .banner
+		height 100px
+		background-color #f9f4f4
+		background-position center
+		background-size cover
 
 	> .avatar
 		display block
-		float left
-		margin 0 16px 0 0
-		width 58px
-		height 58px
-		border-radius 8px
+		margin -40px auto 0 auto
+		width 80px
+		height 80px
+		border-radius 100%
+		border solid 4px $bg
 
-	> .main
-		float left
-		width calc(100% - 74px)
+	> .body
+		padding 4px 32px 32px 32px
 
-		> header
-			margin-bottom 2px
+		@media (max-width 400px)
+			padding 4px 16px 16px 16px
 
-			> .name
-				display inline
-				margin 0
-				padding 0
-				color #777
-				font-size 1em
-				font-weight 700
-				text-align left
-				text-decoration none
+		> .name
+			font-size 20px
+			font-weight bold
 
-				&:hover
-					text-decoration underline
+		> .username
+			display block
+			opacity 0.7
 
-			> .username
-				text-align left
-				margin 0 0 0 8px
-				color #ccc
+		> .description
+			margin 16px 0
 
-		> .body
-			> .followed
-				display inline-block
-				margin 0 0 4px 0
-				padding 2px 8px
-				vertical-align top
-				font-size 10px
-				color #71afc7
-				background #eefaff
-				border-radius 4px
-
-			> .description
-				cursor default
-				display block
-				margin 0
-				padding 0
-				overflow-wrap break-word
-				font-size 1.1em
-				color #717171
-
-	> .mk-follow-button
-		position absolute
-		top 16px
-		right 16px
+		> .followed
+			margin 0 0 16px 0
+			padding 0
+			line-height 24px
+			font-size 0.8em
+			color #71afc7
+			background #eefaff
+			border-radius 4px
 
 </style>
diff --git a/src/client/app/desktop/views/components/users-list.vue b/src/client/app/desktop/views/components/users-list.vue
index 0423db8ed7..05e2f4e5b3 100644
--- a/src/client/app/desktop/views/components/users-list.vue
+++ b/src/client/app/desktop/views/components/users-list.vue
@@ -33,7 +33,7 @@ export default Vue.extend({
 	props: ['fetch', 'count', 'youKnowCount'],
 	data() {
 		return {
-			limit: 30,
+			limit: 20,
 			mode: 'all',
 			fetching: true,
 			moreFetching: false,
@@ -73,10 +73,14 @@ export default Vue.extend({
 
 .mk-users-list
 	height 100%
-	background #fff
+	overflow auto
+	background #eee
 
 	> nav
-		z-index 1
+		z-index 10
+		position sticky
+		top 0
+		background #fff
 		box-shadow 0 1px 0 rgba(#000, 0.1)
 
 		> div
@@ -114,16 +118,14 @@ export default Vue.extend({
 					background #eee
 					border-radius 20px
 
-	> .users
-		height calc(100% - 54px)
-		overflow auto
+	> button
+		display block
+		width calc(100% - 32px)
+		margin 16px
+		padding 16px
 
-		> *
-			border-bottom solid 1px rgba(#000, 0.05)
-
-			> *
-				max-width 600px
-				margin 0 auto
+		&:hover
+			background rgba(#000, 0.1)
 
 	> .no
 		margin 0
diff --git a/src/client/app/desktop/views/components/window.vue b/src/client/app/desktop/views/components/window.vue
index ec044ad27e..30f0ec558f 100644
--- a/src/client/app/desktop/views/components/window.vue
+++ b/src/client/app/desktop/views/components/window.vue
@@ -106,7 +106,7 @@ export default Vue.extend({
 
 	mounted() {
 		if (this.preventMount) {
-			this.$destroy();
+			this.destroyDom();
 			return;
 		}
 
@@ -190,7 +190,7 @@ export default Vue.extend({
 			});
 
 			setTimeout(() => {
-				this.$destroy();
+				this.destroyDom();
 				this.$emit('closed');
 			}, 300);
 		},
diff --git a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
index ebb54d782e..c86c30db17 100644
--- a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
+++ b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
@@ -1,22 +1,34 @@
 <template>
 <div class="obdskegsannmntldydackcpzezagxqfy mk-admin-card">
 	<header>%i18n:@dashboard%</header>
+
 	<div v-if="stats" class="stats">
 		<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div>
 		<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div>
 		<div><b>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div>
 		<div><span>%fa:pencil-alt% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div>
 	</div>
+
 	<div class="cpu-memory">
 		<x-cpu-memory :connection="connection"/>
 	</div>
-	<div>
-		<label>
-			<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
-			<span>disableRegistration</span>
-		</label>
-		<button class="ui" @click="invite">%i18n:@invite%</button>
-		<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
+
+	<div class="form">
+		<div>
+			<label>
+				<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
+				<span>%i18n:@disableRegistration%</span>
+			</label>
+			<button class="ui" @click="invite">%i18n:@invite%</button>
+			<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
+		</div>
+
+		<div>
+			<label>
+				<input type="checkbox" v-model="disableLocalTimeline" @change="updateMeta">
+				<span>%i18n:@disableLocalTimeline%</span>
+			</label>
+		</div>
 	</div>
 </div>
 </template>
@@ -33,6 +45,7 @@ export default Vue.extend({
 		return {
 			stats: null,
 			disableRegistration: false,
+			disableLocalTimeline: false,
 			inviteCode: null,
 			connection: null,
 			connectionId: null
@@ -44,6 +57,7 @@ export default Vue.extend({
 
 		(this as any).os.getMeta().then(meta => {
 			this.disableRegistration = meta.disableRegistration;
+			this.disableLocalTimeline = meta.disableLocalTimeline;
 		});
 
 		(this as any).api('stats').then(stats => {
@@ -61,7 +75,8 @@ export default Vue.extend({
 		},
 		updateMeta() {
 			(this as any).api('admin/update-meta', {
-				disableRegistration: this.disableRegistration
+				disableRegistration: this.disableRegistration,
+				disableLocalTimeline: this.disableLocalTimeline
 			});
 		}
 	}
@@ -97,4 +112,8 @@ export default Vue.extend({
 		border solid 1px #eee
 		border-radius: 8px
 
+	> .form
+		> div
+			border-bottom solid 1px #eee
+
 </style>
diff --git a/src/client/app/desktop/views/pages/admin/admin.hashtags.vue b/src/client/app/desktop/views/pages/admin/admin.hashtags.vue
new file mode 100644
index 0000000000..c6bf20361f
--- /dev/null
+++ b/src/client/app/desktop/views/pages/admin/admin.hashtags.vue
@@ -0,0 +1,41 @@
+<template>
+<div class="jdnqwkzlnxcfftthoybjxrebyolvoucw mk-admin-card">
+	<header>%i18n:@hided-tags%</header>
+	<textarea v-model="hidedTags"></textarea>
+	<button class="ui" @click="save">%i18n:@save%</button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+
+export default Vue.extend({
+	data() {
+		return {
+			hidedTags: '',
+		};
+	},
+	created() {
+		(this as any).os.getMeta().then(meta => {
+			this.hidedTags = meta.hidedTags.join('\n');
+		});
+	},
+	methods: {
+		save() {
+			(this as any).api('admin/update-meta', {
+				hidedTags: this.hidedTags.split('\n')
+			});
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+.jdnqwkzlnxcfftthoybjxrebyolvoucw
+	textarea
+		width 100%
+		min-height 300px
+
+</style>
diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue
index a71059c378..510252b447 100644
--- a/src/client/app/desktop/views/pages/admin/admin.vue
+++ b/src/client/app/desktop/views/pages/admin/admin.vue
@@ -5,6 +5,8 @@
 			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
 			<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
 			<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
+			<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
+
 			<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
 			<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
 		</ul>
@@ -17,6 +19,9 @@
 		<div v-show="page == 'announcements'">
 			<x-announcements/>
 		</div>
+		<div v-show="page == 'hashtags'">
+			<x-hashtags/>
+		</div>
 		<div v-if="page == 'users'">
 			<x-suspend-user/>
 			<x-unsuspend-user/>
@@ -33,6 +38,7 @@
 import Vue from "vue";
 import XDashboard from "./admin.dashboard.vue";
 import XAnnouncements from "./admin.announcements.vue";
+import XHashtags from "./admin.hashtags.vue";
 import XSuspendUser from "./admin.suspend-user.vue";
 import XUnsuspendUser from "./admin.unsuspend-user.vue";
 import XVerifyUser from "./admin.verify-user.vue";
@@ -43,6 +49,7 @@ export default Vue.extend({
 	components: {
 		XDashboard,
 		XAnnouncements,
+		XHashtags,
 		XSuspendUser,
 		XUnsuspendUser,
 		XVerifyUser,
diff --git a/src/client/app/desktop/views/pages/deck/deck.column.vue b/src/client/app/desktop/views/pages/deck/deck.column.vue
index 239b1b0447..abb09775fb 100644
--- a/src/client/app/desktop/views/pages/deck/deck.column.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.column.vue
@@ -28,6 +28,7 @@
 import Vue from 'vue';
 import Menu from '../../../../common/views/components/menu.vue';
 import contextmenu from '../../../api/contextmenu';
+import { countIf } from '../../../../../../prelude/array';
 
 export default Vue.extend({
 	props: {
@@ -117,7 +118,7 @@ export default Vue.extend({
 		toggleActive() {
 			if (!this.isStacked) return;
 			const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id));
-			if (this.active && vms.filter(vm => vm.$el.classList.contains('active')).length == 1) return;
+			if (this.active && countIf(vm => vm.$el.classList.contains('active'), vms) == 1) return;
 			this.active = !this.active;
 		},
 
diff --git a/src/client/app/desktop/views/pages/deck/deck.list-tl.vue b/src/client/app/desktop/views/pages/deck/deck.list-tl.vue
index 70048f99e3..e82e76e4d0 100644
--- a/src/client/app/desktop/views/pages/deck/deck.list-tl.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.list-tl.vue
@@ -68,7 +68,7 @@ export default Vue.extend({
 				(this as any).api('notes/user-list-timeline', {
 					listId: this.list.id,
 					limit: fetchLimit + 1,
-					mediaOnly: this.mediaOnly,
+					withFiles: this.mediaOnly,
 					includeMyRenotes: this.$store.state.settings.showMyRenotes,
 					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 					includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@@ -90,7 +90,7 @@ export default Vue.extend({
 				listId: this.list.id,
 				limit: fetchLimit + 1,
 				untilId: (this.$refs.timeline as any).tail().id,
-				mediaOnly: this.mediaOnly,
+				withFiles: this.mediaOnly,
 				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 				includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@@ -109,7 +109,7 @@ export default Vue.extend({
 			return promise;
 		},
 		onNote(note) {
-			if (this.mediaOnly && note.media.length == 0) return;
+			if (this.mediaOnly && note.files.length == 0) return;
 
 			// Prepend a note
 			(this.$refs.timeline as any).prepend(note);
diff --git a/src/client/app/desktop/views/pages/deck/deck.note.vue b/src/client/app/desktop/views/pages/deck/deck.note.vue
index 2615c0d090..980fb03136 100644
--- a/src/client/app/desktop/views/pages/deck/deck.note.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.note.vue
@@ -18,7 +18,7 @@
 			<div class="body">
 				<p v-if="p.cw != null" class="cw">
 					<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
-					<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@less%' : '%i18n:@more%' }}</span>
+					<mk-cw-button v-model="showContent"/>
 				</p>
 				<div class="content" v-show="p.cw == null || showContent">
 					<div class="text">
@@ -28,8 +28,8 @@
 						<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
 						<a class="rp" v-if="p.renote != null">RP:</a>
 					</div>
-					<div class="media" v-if="p.media.length > 0">
-						<mk-media-list :media-list="p.media"/>
+					<div class="files" v-if="p.files.length > 0">
+						<mk-media-list :media-list="p.files"/>
 					</div>
 					<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
 					<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
@@ -54,11 +54,11 @@
 	</article>
 </div>
 <div v-else class="srwrkujossgfuhrbnvqkybtzxpblgchi">
-	<div v-if="note.media.length > 0">
-		<mk-media-list :media-list="note.media"/>
+	<div v-if="note.files.length > 0">
+		<mk-media-list :media-list="note.files"/>
 	</div>
-	<div v-if="note.renote && note.renote.media.length > 0">
-		<mk-media-list :media-list="note.renote.media"/>
+	<div v-if="note.renote && note.renote.files.length > 0">
+		<mk-media-list :media-list="note.renote.files"/>
 	</div>
 </div>
 </template>
@@ -100,7 +100,7 @@ export default Vue.extend({
 		isRenote(): boolean {
 			return (this.note.renote &&
 				this.note.text == null &&
-				this.note.mediaIds.length == 0 &&
+				this.note.fileIds.length == 0 &&
 				this.note.poll == null);
 		},
 
@@ -371,7 +371,7 @@ root(isDark)
 					.mk-url-preview
 						margin-top 8px
 
-					> .media
+					> .files
 						> img
 							display block
 							max-width 100%
@@ -394,7 +394,7 @@ root(isDark)
 					> .renote
 						margin 8px 0
 
-						> .mk-note-preview
+						> *
 							padding 16px
 							border dashed 1px isDark ? #4e945e : #c0dac6
 							border-radius 8px
diff --git a/src/client/app/desktop/views/pages/deck/deck.notes.vue b/src/client/app/desktop/views/pages/deck/deck.notes.vue
index f7fca5de92..2e7e30f12a 100644
--- a/src/client/app/desktop/views/pages/deck/deck.notes.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.notes.vue
@@ -127,7 +127,7 @@ export default Vue.extend({
 		prepend(note, silent = false) {
 			//#region 弾く
 			const isMyNote = note.userId == this.$store.state.i.id;
-			const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
+			const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
 
 			if (this.$store.state.settings.showMyRenotes === false) {
 				if (isMyNote && isPureRenote) {
diff --git a/src/client/app/desktop/views/pages/deck/deck.tl.vue b/src/client/app/desktop/views/pages/deck/deck.tl.vue
index a9e4d489c3..120ceb7fc2 100644
--- a/src/client/app/desktop/views/pages/deck/deck.tl.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.tl.vue
@@ -96,7 +96,7 @@ export default Vue.extend({
 			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 				(this as any).api(this.endpoint, {
 					limit: fetchLimit + 1,
-					mediaOnly: this.mediaOnly,
+					withFiles: this.mediaOnly,
 					includeMyRenotes: this.$store.state.settings.showMyRenotes,
 					includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
 					includeLocalRenotes: this.$store.state.settings.showLocalRenotes
@@ -117,7 +117,7 @@ export default Vue.extend({
 
 			const promise = (this as any).api(this.endpoint, {
 				limit: fetchLimit + 1,
-				mediaOnly: this.mediaOnly,
+				withFiles: this.mediaOnly,
 				untilId: (this.$refs.timeline as any).tail().id,
 				includeMyRenotes: this.$store.state.settings.showMyRenotes,
 				includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
@@ -138,7 +138,7 @@ export default Vue.extend({
 		},
 
 		onNote(note) {
-			if (this.mediaOnly && note.media.length == 0) return;
+			if (this.mediaOnly && note.files.length == 0) return;
 
 			// Prepend a note
 			(this.$refs.timeline as any).prepend(note);
diff --git a/src/client/app/desktop/views/pages/deck/deck.vue b/src/client/app/desktop/views/pages/deck/deck.vue
index 26b989656e..5e7a07ea6b 100644
--- a/src/client/app/desktop/views/pages/deck/deck.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.vue
@@ -85,6 +85,7 @@ export default Vue.extend({
 	},
 
 	mounted() {
+		document.title = (this as any).os.instanceName;
 		document.documentElement.style.overflow = 'hidden';
 	},
 
diff --git a/src/client/app/desktop/views/pages/user/user.header.vue b/src/client/app/desktop/views/pages/user/user.header.vue
index d8f4656ed0..4b434ec219 100644
--- a/src/client/app/desktop/views/pages/user/user.header.vue
+++ b/src/client/app/desktop/views/pages/user/user.header.vue
@@ -6,7 +6,7 @@
 		<div class="title">
 			<p class="name">{{ user | userName }}</p>
 			<div>
-				<span class="username"><mk-acct :user="user"/></span>
+				<span class="username"><mk-acct :user="user" :detail="true" /></span>
 				<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
 				<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
 				<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
diff --git a/src/client/app/desktop/views/pages/user/user.photos.vue b/src/client/app/desktop/views/pages/user/user.photos.vue
index 64c537f1ed..c5cd9e24fe 100644
--- a/src/client/app/desktop/views/pages/user/user.photos.vue
+++ b/src/client/app/desktop/views/pages/user/user.photos.vue
@@ -24,12 +24,12 @@ export default Vue.extend({
 	mounted() {
 		(this as any).api('users/notes', {
 			userId: this.user.id,
-			withMedia: true,
+			withFiles: true,
 			limit: 9
 		}).then(notes => {
 			notes.forEach(note => {
-				note.media.forEach(media => {
-					if (this.images.length < 9) this.images.push(media);
+				note.files.forEach(file => {
+					if (this.images.length < 9) this.images.push(file);
 				});
 			});
 			this.fetching = false;
diff --git a/src/client/app/desktop/views/pages/user/user.timeline.vue b/src/client/app/desktop/views/pages/user/user.timeline.vue
index 67987fcb94..54221380a7 100644
--- a/src/client/app/desktop/views/pages/user/user.timeline.vue
+++ b/src/client/app/desktop/views/pages/user/user.timeline.vue
@@ -66,7 +66,7 @@ export default Vue.extend({
 					limit: fetchLimit + 1,
 					untilDate: this.date ? this.date.getTime() : undefined,
 					includeReplies: this.mode == 'with-replies',
-					withMedia: this.mode == 'with-media'
+					withFiles: this.mode == 'with-media'
 				}).then(notes => {
 					if (notes.length == fetchLimit + 1) {
 						notes.pop();
@@ -86,7 +86,7 @@ export default Vue.extend({
 				userId: this.user.id,
 				limit: fetchLimit + 1,
 				includeReplies: this.mode == 'with-replies',
-				withMedia: this.mode == 'with-media',
+				withFiles: this.mode == 'with-media',
 				untilId: (this.$refs.timeline as any).tail().id
 			});
 
diff --git a/src/client/app/desktop/views/pages/welcome.vue b/src/client/app/desktop/views/pages/welcome.vue
index 0bc5c256e0..ea1734f8c7 100644
--- a/src/client/app/desktop/views/pages/welcome.vue
+++ b/src/client/app/desktop/views/pages/welcome.vue
@@ -7,45 +7,130 @@
 
 	<mk-forkit class="forkit"/>
 
-	<div class="body">
-		<div class="main block">
-			<h1 v-if="name != 'Misskey'">{{ name }}</h1>
-			<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
+	<main>
+		<div class="body">
+			<div class="main block">
+				<div>
+					<h1 v-if="name != 'Misskey'">{{ name }}</h1>
+					<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
 
-			<div class="info">
-				<span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span>
-				<span class="stats" v-if="stats">
-					<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
-					<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
-				</span>
+					<div class="info">
+						<span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span>
+						<span class="stats" v-if="stats">
+							<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
+							<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
+						</span>
+					</div>
+
+					<div class="desc">
+						<span class="desc" v-html="description || '%i18n:common.about%'"></span>
+						<a class="about" @click="about">%i18n:@about%</a>
+					</div>
+
+					<p class="sign">
+						<span class="signup" @click="signup">%i18n:@signup%</span>
+						<span class="divider">|</span>
+						<span class="signin" @click="signin">%i18n:@signin%</span>
+					</p>
+
+					<img src="/assets/ai.png" alt="" title="藍" class="char">
+				</div>
 			</div>
 
-			<p class="desc" v-html="description || '%i18n:common.about%'"></p>
+			<div class="announcements block">
+				<header>%fa:broadcast-tower% %i18n:@announcements%</header>
+				<div v-if="announcements && announcements.length > 0">
+					<div v-for="announcement in announcements">
+						<h1 v-html="announcement.title"></h1>
+						<div v-html="announcement.text"></div>
+					</div>
+				</div>
+			</div>
 
-			<p class="sign">
-				<span class="signup" @click="signup">%i18n:@signup%</span>
-				<span class="divider">|</span>
-				<span class="signin" @click="signin">%i18n:@signin%</span>
-			</p>
-		</div>
+			<div class="photos block">
+				<header>%fa:images% %i18n:@photos%</header>
+				<div>
+					<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
+				</div>
+			</div>
 
-		<div class="broadcasts block">
-			<div v-for="broadcast in broadcasts">
-				<h1 v-html="broadcast.title"></h1>
-				<div v-html="broadcast.text"></div>
+			<div class="tag-cloud block">
+				<div>
+					<mk-tag-cloud/>
+				</div>
+			</div>
+
+			<div class="nav block">
+				<div>
+					<mk-nav class="nav"/>
+				</div>
+			</div>
+
+			<div class="side">
+				<div class="trends block">
+					<div>
+						<mk-trends/>
+					</div>
+				</div>
+
+				<div class="tl block">
+					<header>%fa:comment-alt R% %i18n:@timeline%</header>
+					<div>
+						<mk-welcome-timeline class="tl" :max="20"/>
+					</div>
+				</div>
+
+				<div class="info block">
+					<header>%fa:info-circle% %i18n:@info%</header>
+					<div>
+						<div v-if="meta" class="body">
+							<p>Version: <b>{{ meta.version }}</b></p>
+							<p>Maintainer: <b><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></b></p>
+						</div>
+					</div>
+				</div>
 			</div>
 		</div>
+	</main>
 
-		<div class="nav block">
-			<mk-nav class="nav"/>
-		</div>
-
-		<div class="side">
-			<mk-trends class="trends block"/>
-
-			<mk-welcome-timeline class="tl block" :max="20"/>
-		</div>
-	</div>
+	<modal name="about" :class="$store.state.device.darkmode ? ['about', 'modal-dark'] : ['about', 'modal-light']" width="800px" height="auto" scrollable>
+		<article class="fpdezooorhntlzyeszemrsqdlgbysvxq">
+			<h1>%i18n:common.intro.title%</h1>
+			<p v-html="'%i18n:common.intro.about%'"></p>
+			<section>
+				<h2>%i18n:common.intro.features%</h2>
+				<section>
+					<div class="body">
+						<h3>%i18n:common.intro.rich-contents%</h3>
+						<p v-html="'%i18n:common.intro.rich-contents-desc%'"></p>
+					</div>
+					<div class="image"><img src="/assets/about/post.png" alt=""></div>
+				</section>
+				<section>
+					<div class="body">
+						<h3>%i18n:common.intro.reaction%</h3>
+						<p v-html="'%i18n:common.intro.reaction-desc%'"></p>
+					</div>
+					<div class="image"><img src="/assets/about/reaction.png" alt=""></div>
+				</section>
+				<section>
+					<div class="body">
+						<h3>%i18n:common.intro.ui%</h3>
+						<p v-html="'%i18n:common.intro.ui-desc%'"></p>
+					</div>
+					<div class="image"><img src="/assets/about/ui.png" alt=""></div>
+				</section>
+				<section>
+					<div class="body">
+						<h3>%i18n:common.intro.drive%</h3>
+						<p v-html="'%i18n:common.intro.drive-desc%'"></p>
+					</div>
+					<div class="image"><img src="/assets/about/drive.png" alt=""></div>
+				</section>
+			</section>
+			<p v-html="'%i18n:common.intro.outro%'"></p>
+		</article>
+	</modal>
 
 	<modal name="signup" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable>
 		<header class="formHeader">%i18n:@signup%</header>
@@ -62,37 +147,62 @@
 <script lang="ts">
 import Vue from 'vue';
 import { host, copyright } from '../../../config';
+import { concat } from '../../../../../prelude/array';
 
 export default Vue.extend({
 	data() {
 		return {
+			meta: null,
 			stats: null,
 			copyright,
 			host,
 			name: 'Misskey',
 			description: '',
-			broadcasts: []
+			announcements: [],
+			photos: []
 		};
 	},
+
 	created() {
 		(this as any).os.getMeta().then(meta => {
+			this.meta = meta;
 			this.name = meta.name;
 			this.description = meta.description;
-			this.broadcasts = meta.broadcasts;
+			this.announcements = meta.broadcasts;
 		});
 
 		(this as any).api('stats').then(stats => {
 			this.stats = stats;
 		});
 
+		const image = [
+			'image/jpeg',
+			'image/png',
+			'image/gif'
+		];
+
+		(this as any).api('notes/local-timeline', {
+			fileType: image,
+			limit: 6
+		}).then((notes: any[]) => {
+			const files = concat(notes.map((n: any): any[] => n.files));
+			this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
+		});
 	},
+
 	methods: {
+		about() {
+			this.$modal.show('about');
+		},
+
 		signup() {
 			this.$modal.show('signup');
 		},
+
 		signin() {
 			this.$modal.show('signin');
 		},
+
 		dark() {
 			this.$store.commit('device/set', {
 				key: 'darkmode',
@@ -137,6 +247,54 @@ export default Vue.extend({
 		margin 0 48px
 		font-size 1.5em
 
+.v--modal-overlay.about
+	.v--modal-box.v--modal
+		margin 32px 0
+
+.fpdezooorhntlzyeszemrsqdlgbysvxq
+	padding 64px
+
+	> p:last-child
+		margin-bottom 0
+
+	> h1
+		margin-top 0
+
+	> section
+		> h2
+			border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
+
+		> section
+			display grid
+			grid-template-rows 1fr
+			grid-template-columns 180px 1fr
+			gap 32px
+			margin-bottom 32px
+			padding-bottom 32px
+			border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
+
+			&:nth-child(odd)
+				grid-template-columns 1fr 180px
+
+				> .body
+					grid-column 1
+
+				> .image
+					grid-column 2
+
+			> .body
+				grid-row 1
+				grid-column 2
+
+			> .image
+				grid-row 1
+				grid-column 1
+
+				> img
+					display block
+					width 100%
+					height 100%
+					object-fit cover
 </style>
 
 <style lang="stylus" scoped>
@@ -164,116 +322,176 @@ root(isDark)
 		font-size 18px
 		color isDark ? #fff : #444
 
-	> .body
-		display grid
-		grid-template-rows 0.5fr 0.5fr 64px
-		grid-template-columns 1fr 350px
-		gap 16px
-		width 100%
-		max-width 1200px
-		height 100vh
-		min-height 800px
+	> main
 		margin 0 auto
 		padding 64px
+		width 100%
+		max-width 1200px
 
 		.block
 			color isDark ? #fff : #444
-			background isDark ? #313543 : #fff
+			background isDark ? #282C37 : #fff
 			box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
 			//border-radius 8px
 			overflow auto
 
-		> .main
-			grid-row 1
-			grid-column 1
-			padding 32px
-			border-top solid 5px $theme-color
+			> header
+				z-index 1
+				padding 0 16px
+				line-height 48px
+				background isDark ? #313543 : #fff
 
-			> h1
-				margin 0
+				if !isDark
+					box-shadow 0 1px 0px rgba(0, 0, 0, 0.1)
 
-				> img
-					margin -8px 0 0 -16px
-					max-width 280px
-
-			> .info
-				margin 0 auto 16px auto
-				width $width
-				font-size 14px
-
-				> .stats
-					margin-left 16px
-					padding-left 16px
-					border-left solid 1px isDark ? #fff : #444
-
-					> *
-						margin-right 16px
-
-			> .sign
-				font-size 120%
-
-				> .divider
-					margin 0 16px
-
-				> .signin
-				> .signup
-					cursor pointer
-
-					&:hover
-						color $theme-color
-
-			> .hashtags
-				margin 16px auto
-				width $width
-				font-size 14px
-				background rgba(#000, 0.3)
-				border-radius 8px
-
-				> *
-					display inline-block
-					margin 14px
-
-		> .broadcasts
-			grid-row 2
-			grid-column 1
-			padding 32px
+				& + div
+					max-height calc(100% - 48px)
 
 			> div
-				padding 0 0 16px 0
-				margin 0 0 16px 0
-				border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
-
-				> h1
-					margin 0
-					font-size 1.5em
-
-		> .nav
-			display flex
-			justify-content center
-			align-items center
-			grid-row 3
-			grid-column 1
-			font-size 14px
-
-		> .side
-			display grid
-			grid-row 1 / 4
-			grid-column 2
-			grid-template-rows 1fr 350px
-			grid-template-columns 1fr
-			gap 16px
-
-			> .tl
-				grid-row 1
-				grid-column 1
-				text-align left
-				max-height 100%
 				overflow auto
 
-			> .trends
+		> .body
+			display grid
+			grid-template-rows 390px 1fr 256px 64px
+			grid-template-columns 1fr 1fr 350px
+			gap 16px
+			height 1150px
+
+			> .main
+				grid-row 1
+				grid-column 1 / 3
+				border-top solid 5px $theme-color
+
+				> div
+					padding 32px
+					min-height 100%
+
+					> h1
+						margin 0
+
+						> img
+							margin -8px 0 0 -16px
+							max-width 280px
+
+					> .info
+						margin 0 auto 16px auto
+						width $width
+						font-size 14px
+
+						> .stats
+							margin-left 16px
+							padding-left 16px
+							border-left solid 1px isDark ? #fff : #444
+
+							> *
+								margin-right 16px
+
+					> .desc
+						max-width calc(100% - 150px)
+
+					> .sign
+						font-size 120%
+						margin-bottom 0
+
+						> .divider
+							margin 0 16px
+
+						> .signin
+						> .signup
+							cursor pointer
+
+							&:hover
+								color $theme-color
+
+					> .char
+						display block
+						position absolute
+						right 16px
+						bottom 0
+						height 320px
+						opacity 0.7
+
+					> *:not(.char)
+						z-index 1
+
+			> .announcements
 				grid-row 2
 				grid-column 1
-				padding 8px
+
+				> div
+					padding 32px
+
+					> div
+						padding 0 0 16px 0
+						margin 0 0 16px 0
+						border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
+
+						> h1
+							margin 0
+							font-size 1.25em
+
+			> .photos
+				grid-row 2
+				grid-column 2
+
+				> div
+					display grid
+					grid-template-rows 1fr 1fr 1fr
+					grid-template-columns 1fr 1fr
+					gap 8px
+					height 100%
+					padding 16px
+
+					> div
+						//border-radius 4px
+						background-position center center
+						background-size cover
+
+			> .tag-cloud
+				grid-row 3
+				grid-column 1 / 3
+
+				> div
+					height 256px
+					padding 32px
+
+			> .nav
+				display flex
+				justify-content center
+				align-items center
+				grid-row 4
+				grid-column 1 / 3
+				font-size 14px
+
+			> .side
+				display grid
+				grid-row 1 / 5
+				grid-column 3
+				grid-template-rows 1fr 350px
+				grid-template-columns 1fr
+				gap 16px
+
+				> .tl
+					grid-row 1
+					grid-column 1
+					overflow auto
+
+				> .trends
+					grid-row 2
+					grid-column 1
+					padding 8px
+
+				> .info
+					grid-row 3
+					grid-column 1
+
+					> div
+						padding 16px
+
+						> .body
+							> p
+								display block
+								margin 0
 
 .mk-welcome[data-darkmode]
 	root(true)
diff --git a/src/client/app/desktop/views/widgets/trends.vue b/src/client/app/desktop/views/widgets/trends.vue
index c33bf2f2f2..aeaab63ac4 100644
--- a/src/client/app/desktop/views/widgets/trends.vue
+++ b/src/client/app/desktop/views/widgets/trends.vue
@@ -49,7 +49,7 @@ export default define({
 				offset: this.offset,
 				renote: false,
 				reply: false,
-				media: false,
+				file: false,
 				poll: false
 			}).then(notes => {
 				const note = notes ? notes[0] : null;
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index 82924e92e3..db3852da60 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -7,9 +7,6 @@ import Vuex from 'vuex';
 import VueRouter from 'vue-router';
 import * as TreeView from 'vue-json-tree-view';
 import VAnimateCss from 'v-animate-css';
-import Element from 'element-ui';
-import ElementLocaleEn from 'element-ui/lib/locale/lang/en';
-import ElementLocaleJa from 'element-ui/lib/locale/lang/ja';
 import VModal from 'vue-js-modal';
 
 import App from './app.vue';
@@ -17,18 +14,10 @@ import checkForUpdate from './common/scripts/check-for-update';
 import MiOS, { API } from './mios';
 import { version, codename, lang } from './config';
 
-let elementLocale;
-switch (lang) {
-	case 'ja-JP': elementLocale = ElementLocaleJa; break;
-	case 'en-US': elementLocale = ElementLocaleEn; break;
-	default: elementLocale = ElementLocaleEn; break;
-}
-
 Vue.use(Vuex);
 Vue.use(VueRouter);
 Vue.use(TreeView);
 Vue.use(VAnimateCss);
-Vue.use(Element, { locale: elementLocale });
 Vue.use(VModal);
 
 // Register global directives
@@ -42,9 +31,13 @@ require('./common/views/widgets');
 require('./common/views/filters');
 
 Vue.mixin({
-	destroyed(this: any) {
-		if (this.$el.parentNode) {
-			this.$el.parentNode.removeChild(this.$el);
+	methods: {
+		destroyDom() {
+			this.$destroy();
+
+			if (this.$el.parentNode) {
+				this.$el.parentNode.removeChild(this.$el);
+			}
 		}
 	}
 });
diff --git a/src/client/app/mios.ts b/src/client/app/mios.ts
index c2ec7f1750..0f72cd2f34 100644
--- a/src/client/app/mios.ts
+++ b/src/client/app/mios.ts
@@ -17,6 +17,7 @@ import Err from './common/views/components/connect-failed.vue';
 import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline';
 import { HybridTimelineStreamManager } from './common/scripts/streaming/hybrid-timeline';
 import { GlobalTimelineStreamManager } from './common/scripts/streaming/global-timeline';
+import { erase } from '../../prelude/array';
 
 //#region api requests
 let spinner = null;
@@ -537,7 +538,7 @@ export default class MiOS extends EventEmitter {
 	}
 
 	public unregisterStreamConnection(connection: Connection) {
-		this.connections = this.connections.filter(c => c != connection);
+		this.connections = erase(connection, this.connections);
 	}
 }
 
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index 5b9d45462a..9412c85980 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -6,7 +6,6 @@ import VueRouter from 'vue-router';
 
 // Style
 import './style.styl';
-import '../../element.scss';
 
 import init from '../init';
 
diff --git a/src/client/app/mobile/views/components/dialog.vue b/src/client/app/mobile/views/components/dialog.vue
index 9ee01cb782..6a0d74c752 100644
--- a/src/client/app/mobile/views/components/dialog.vue
+++ b/src/client/app/mobile/views/components/dialog.vue
@@ -78,7 +78,7 @@ export default Vue.extend({
 				scale: 0.8,
 				duration: 300,
 				easing: [ 0.5, -0.5, 1, 0.5 ],
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		},
 		onBgClick() {
diff --git a/src/client/app/mobile/views/components/drive-file-chooser.vue b/src/client/app/mobile/views/components/drive-file-chooser.vue
index aaa707d8a7..92ac211af2 100644
--- a/src/client/app/mobile/views/components/drive-file-chooser.vue
+++ b/src/client/app/mobile/views/components/drive-file-chooser.vue
@@ -31,15 +31,15 @@ export default Vue.extend({
 		},
 		onSelected(file) {
 			this.$emit('selected', file);
-			this.$destroy();
+			this.destroyDom();
 		},
 		cancel() {
 			this.$emit('canceled');
-			this.$destroy();
+			this.destroyDom();
 		},
 		ok() {
 			this.$emit('selected', this.files);
-			this.$destroy();
+			this.destroyDom();
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/drive-folder-chooser.vue b/src/client/app/mobile/views/components/drive-folder-chooser.vue
index 7934fb7816..6d3fba1efd 100644
--- a/src/client/app/mobile/views/components/drive-folder-chooser.vue
+++ b/src/client/app/mobile/views/components/drive-folder-chooser.vue
@@ -19,11 +19,11 @@ export default Vue.extend({
 	methods: {
 		cancel() {
 			this.$emit('canceled');
-			this.$destroy();
+			this.destroyDom();
 		},
 		ok() {
 			this.$emit('selected', (this.$refs.browser as any).folder);
-			this.$destroy();
+			this.destroyDom();
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/drive.file-detail.vue b/src/client/app/mobile/views/components/drive.file-detail.vue
index 43867211e9..8108892597 100644
--- a/src/client/app/mobile/views/components/drive.file-detail.vue
+++ b/src/client/app/mobile/views/components/drive.file-detail.vue
@@ -67,7 +67,7 @@
 import Vue from 'vue';
 import * as EXIF from 'exif-js';
 import * as hljs from 'highlight.js';
-import gcd from '../../../common/scripts/gcd';
+import { gcd } from '../../../../../prelude/math';
 
 export default Vue.extend({
 	props: ['file'],
diff --git a/src/client/app/mobile/views/components/friends-maker.vue b/src/client/app/mobile/views/components/friends-maker.vue
index e0461d2bc2..dbb82f4b18 100644
--- a/src/client/app/mobile/views/components/friends-maker.vue
+++ b/src/client/app/mobile/views/components/friends-maker.vue
@@ -47,7 +47,7 @@ export default Vue.extend({
 			this.fetch();
 		},
 		close() {
-			this.$destroy();
+			this.destroyDom();
 		}
 	}
 });
diff --git a/src/client/app/mobile/views/components/media-image.vue b/src/client/app/mobile/views/components/media-image.vue
index c8766f5464..e38cef06ba 100644
--- a/src/client/app/mobile/views/components/media-image.vue
+++ b/src/client/app/mobile/views/components/media-image.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="image.isSensitive && hide" @click="hide = false">
+<div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="image.isSensitive && hide && !$store.state.device.alwaysShowNsfw" @click="hide = false">
 	<div>
 		<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
 		<span>%i18n:@click-to-show%</span>
diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue
index 786e57bb22..68be9f8ac4 100644
--- a/src/client/app/mobile/views/components/note-detail.vue
+++ b/src/client/app/mobile/views/components/note-detail.vue
@@ -35,20 +35,26 @@
 			</div>
 		</header>
 		<div class="body">
-			<div class="text">
-				<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
-				<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
-				<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
-			</div>
-			<div class="media" v-if="p.media.length > 0">
-				<mk-media-list :media-list="p.media" :raw="true"/>
-			</div>
-			<mk-poll v-if="p.poll" :note="p"/>
-			<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
-			<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
-			<div class="map" v-if="p.geo" ref="map"></div>
-			<div class="renote" v-if="p.renote">
-				<mk-note-preview :note="p.renote"/>
+			<p v-if="p.cw != null" class="cw">
+				<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
+				<mk-cw-button v-model="showContent"/>
+			</p>
+			<div class="content" v-show="p.cw == null || showContent">
+				<div class="text">
+					<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
+					<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
+					<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
+				</div>
+				<div class="files" v-if="p.files.length > 0">
+					<mk-media-list :media-list="p.files" :raw="true"/>
+				</div>
+				<mk-poll v-if="p.poll" :note="p"/>
+				<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
+				<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
+				<div class="map" v-if="p.geo" ref="map"></div>
+				<div class="renote" v-if="p.renote">
+					<mk-note-preview :note="p.renote"/>
+				</div>
 			</div>
 		</div>
 		<router-link class="time" :to="p | notePage">
@@ -85,6 +91,7 @@ import parse from '../../../../../mfm/parse';
 import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 import XSub from './note.sub.vue';
+import { sum } from '../../../../../prelude/array';
 
 export default Vue.extend({
 	components: {
@@ -103,6 +110,7 @@ export default Vue.extend({
 
 	data() {
 		return {
+			showContent: false,
 			conversation: [],
 			conversationFetching: false,
 			replies: []
@@ -113,7 +121,7 @@ export default Vue.extend({
 		isRenote(): boolean {
 			return (this.note.renote &&
 				this.note.text == null &&
-				this.note.mediaIds.length == 0 &&
+				this.note.fileIds.length == 0 &&
 				this.note.poll == null);
 		},
 
@@ -123,9 +131,7 @@ export default Vue.extend({
 
 		reactionsCount(): number {
 			return this.p.reactionCounts
-				? Object.keys(this.p.reactionCounts)
-					.map(key => this.p.reactionCounts[key])
-					.reduce((a, b) => a + b)
+				? sum(Object.values(this.p.reactionCounts))
 				: 0;
 		},
 
@@ -335,44 +341,57 @@ root(isDark)
 		> .body
 			padding 8px 0
 
-			> .text
+			> .cw
+				cursor default
 				display block
 				margin 0
 				padding 0
 				overflow-wrap break-word
-				font-size 16px
 				color isDark ? #fff : #717171
 
-				@media (min-width 500px)
-					font-size 24px
+				> .text
+					margin-right 8px
 
-			> .renote
-				margin 8px 0
+			> .content
 
-				> .mk-note-preview
-					padding 16px
-					border dashed 1px #c0dac6
-					border-radius 8px
-
-			> .location
-				margin 4px 0
-				font-size 12px
-				color #ccc
-
-			> .map
-				width 100%
-				height 200px
-
-				&:empty
-					display none
-
-			> .mk-url-preview
-				margin-top 8px
-
-			> .media
-				> img
+				> .text
 					display block
-					max-width 100%
+					margin 0
+					padding 0
+					overflow-wrap break-word
+					font-size 16px
+					color isDark ? #fff : #717171
+
+					@media (min-width 500px)
+						font-size 24px
+
+				> .renote
+					margin 8px 0
+
+					> *
+						padding 16px
+						border dashed 1px #c0dac6
+						border-radius 8px
+
+				> .location
+					margin 4px 0
+					font-size 12px
+					color #ccc
+
+				> .map
+					width 100%
+					height 200px
+
+					&:empty
+						display none
+
+				> .mk-url-preview
+					margin-top 8px
+
+				> .files
+					> img
+						display block
+						max-width 100%
 
 		> .time
 			font-size 16px
diff --git a/src/client/app/mobile/views/components/note-preview.vue b/src/client/app/mobile/views/components/note-preview.vue
index 5d56d2d326..4c03593a9e 100644
--- a/src/client/app/mobile/views/components/note-preview.vue
+++ b/src/client/app/mobile/views/components/note-preview.vue
@@ -1,10 +1,16 @@
 <template>
-<div class="mk-note-preview" :class="{ smart: $store.state.device.postStyle == 'smart' }">
+<div class="yohlumlkhizgfkvvscwfcrcggkotpvry" :class="{ smart: $store.state.device.postStyle == 'smart' }">
 	<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
 	<div class="main">
 		<mk-note-header class="header" :note="note" :mini="true"/>
 		<div class="body">
-			<mk-sub-note-content class="text" :note="note"/>
+			<p v-if="note.cw != null" class="cw">
+				<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
+				<mk-cw-button v-model="showContent"/>
+			</p>
+			<div class="content" v-show="note.cw == null || showContent">
+				<mk-sub-note-content class="text" :note="note"/>
+			</div>
 		</div>
 	</div>
 </div>
@@ -14,7 +20,18 @@
 import Vue from 'vue';
 
 export default Vue.extend({
-	props: ['note']
+	props: {
+		note: {
+			type: Object,
+			required: true
+		}
+	},
+
+	data() {
+		return {
+			showContent: false
+		};
+	}
 });
 </script>
 
@@ -65,16 +82,28 @@ root(isDark)
 
 		> .body
 
-			> .text
+			> .cw
 				cursor default
+				display block
 				margin 0
 				padding 0
-				color isDark ? #959ba7 : #717171
+				overflow-wrap break-word
+				color isDark ? #fff : #717171
 
-.mk-note-preview[data-darkmode]
+				> .text
+					margin-right 8px
+
+			> .content
+				> .text
+					cursor default
+					margin 0
+					padding 0
+					color isDark ? #959ba7 : #717171
+
+.yohlumlkhizgfkvvscwfcrcggkotpvry[data-darkmode]
 	root(true)
 
-.mk-note-preview:not([data-darkmode])
+.yohlumlkhizgfkvvscwfcrcggkotpvry:not([data-darkmode])
 	root(false)
 
 </style>
diff --git a/src/client/app/mobile/views/components/note.sub.vue b/src/client/app/mobile/views/components/note.sub.vue
index a68aec40a1..c25f827dad 100644
--- a/src/client/app/mobile/views/components/note.sub.vue
+++ b/src/client/app/mobile/views/components/note.sub.vue
@@ -1,10 +1,16 @@
 <template>
-<div class="sub" :class="{ smart: $store.state.device.postStyle == 'smart' }">
+<div class="zlrxdaqttccpwhpaagdmkawtzklsccam" :class="{ smart: $store.state.device.postStyle == 'smart' }">
 	<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
 	<div class="main">
 		<mk-note-header class="header" :note="note" :mini="true"/>
 		<div class="body">
-			<mk-sub-note-content class="text" :note="note"/>
+			<p v-if="note.cw != null" class="cw">
+				<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
+				<mk-cw-button v-model="showContent"/>
+			</p>
+			<div class="content" v-show="note.cw == null || showContent">
+				<mk-sub-note-content class="text" :note="note"/>
+			</div>
 		</div>
 	</div>
 </div>
@@ -24,6 +30,12 @@ export default Vue.extend({
 			type: Boolean,
 			default: true
 		}
+	},
+
+	data() {
+		return {
+			showContent: false
+		};
 	}
 });
 </script>
@@ -77,20 +89,31 @@ root(isDark)
 			margin-bottom 2px
 
 		> .body
-
-			> .text
+			> .cw
+				cursor default
+				display block
 				margin 0
 				padding 0
-				color isDark ? #959ba7 : #717171
+				overflow-wrap break-word
+				color isDark ? #fff : #717171
 
-				pre
-					max-height 120px
-					font-size 80%
+				> .text
+					margin-right 8px
 
-.sub[data-darkmode]
+			> .content
+				> .text
+					margin 0
+					padding 0
+					color isDark ? #959ba7 : #717171
+
+					pre
+						max-height 120px
+						font-size 80%
+
+.zlrxdaqttccpwhpaagdmkawtzklsccam[data-darkmode]
 	root(true)
 
-.sub:not([data-darkmode])
+.zlrxdaqttccpwhpaagdmkawtzklsccam:not([data-darkmode])
 	root(false)
 
 </style>
diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue
index 258433cb3f..8787b39a93 100644
--- a/src/client/app/mobile/views/components/note.vue
+++ b/src/client/app/mobile/views/components/note.vue
@@ -18,7 +18,7 @@
 			<div class="body">
 				<p v-if="p.cw != null" class="cw">
 					<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
-					<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@less%' : '%i18n:@more%' }}</span>
+					<mk-cw-button v-model="showContent"/>
 				</p>
 				<div class="content" v-show="p.cw == null || showContent">
 					<div class="text">
@@ -28,16 +28,14 @@
 						<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
 						<a class="rp" v-if="p.renote != null">RP:</a>
 					</div>
-					<div class="media" v-if="p.media.length > 0">
-						<mk-media-list :media-list="p.media"/>
+					<div class="files" v-if="p.files.length > 0">
+						<mk-media-list :media-list="p.files"/>
 					</div>
 					<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
 					<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 					<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
 					<div class="map" v-if="p.geo" ref="map"></div>
-					<div class="renote" v-if="p.renote">
-						<mk-note-preview :note="p.renote"/>
-					</div>
+					<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
 				</div>
 				<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
 			</div>
@@ -70,6 +68,7 @@ import parse from '../../../../../mfm/parse';
 import MkNoteMenu from '../../../common/views/components/note-menu.vue';
 import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
 import XSub from './note.sub.vue';
+import { sum } from '../../../../../prelude/array';
 
 export default Vue.extend({
 	components: {
@@ -90,7 +89,7 @@ export default Vue.extend({
 		isRenote(): boolean {
 			return (this.note.renote &&
 				this.note.text == null &&
-				this.note.mediaIds.length == 0 &&
+				this.note.fileIds.length == 0 &&
 				this.note.poll == null);
 		},
 
@@ -100,9 +99,7 @@ export default Vue.extend({
 
 		reactionsCount(): number {
 			return this.p.reactionCounts
-				? Object.keys(this.p.reactionCounts)
-					.map(key => this.p.reactionCounts[key])
-					.reduce((a, b) => a + b)
+				? sum(Object.values(this.p.reactionCounts))
 				: 0;
 		},
 
@@ -353,19 +350,6 @@ root(isDark)
 					> .text
 						margin-right 8px
 
-					> .toggle
-						display inline-block
-						padding 4px 8px
-						font-size 0.7em
-						color isDark ? #393f4f : #fff
-						background isDark ? #687390 : #b1b9c1
-						border-radius 2px
-						cursor pointer
-						user-select none
-
-						&:hover
-							background isDark ? #707b97 : #bbc4ce
-
 				> .content
 
 					> .text
@@ -414,7 +398,7 @@ root(isDark)
 					.mk-url-preview
 						margin-top 8px
 
-					> .media
+					> .files
 						> img
 							display block
 							max-width 100%
@@ -437,7 +421,7 @@ root(isDark)
 					> .renote
 						margin 8px 0
 
-						> .mk-note-preview
+						> *
 							padding 16px
 							border dashed 1px isDark ? #4e945e : #c0dac6
 							border-radius 8px
diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue
index 714e521c0f..17806f062e 100644
--- a/src/client/app/mobile/views/components/notes.vue
+++ b/src/client/app/mobile/views/components/notes.vue
@@ -14,8 +14,7 @@
 	</div>
 
 	<!-- トランジションを有効にするとなぜかメモリリークする -->
-	<!-- <transition-group name="mk-notes" class="transition"> -->
-	<div class="transition">
+	<transition-group name="mk-notes" class="transition" tag="div">
 		<template v-for="(note, i) in _notes">
 			<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
 			<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
@@ -23,8 +22,7 @@
 				<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
 			</p>
 		</template>
-	</div>
-	<!-- </transition-group> -->
+	</transition-group>
 
 	<footer v-if="more">
 		<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
@@ -125,7 +123,7 @@ export default Vue.extend({
 		prepend(note, silent = false) {
 			//#region 弾く
 			const isMyNote = note.userId == this.$store.state.i.id;
-			const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
+			const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
 
 			if (this.$store.state.settings.showMyRenotes === false) {
 				if (isMyNote && isPureRenote) {
diff --git a/src/client/app/mobile/views/components/notify.vue b/src/client/app/mobile/views/components/notify.vue
index 4d9b7c0f6b..5f94b91ddd 100644
--- a/src/client/app/mobile/views/components/notify.vue
+++ b/src/client/app/mobile/views/components/notify.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="mk-notify">
+<div class="mk-notify" :class="pos">
 	<div>
 		<mk-notification-preview :notification="notification"/>
 	</div>
@@ -12,11 +12,16 @@ import * as anime from 'animejs';
 
 export default Vue.extend({
 	props: ['notification'],
+	computed: {
+		pos() {
+			return this.$store.state.device.mobileNotificationPosition;
+		}
+	},
 	mounted() {
 		this.$nextTick(() => {
 			anime({
 				targets: this.$el,
-				bottom: '0px',
+				[this.pos]: '0px',
 				duration: 500,
 				easing: 'easeOutQuad'
 			});
@@ -24,10 +29,10 @@ export default Vue.extend({
 			setTimeout(() => {
 				anime({
 					targets: this.$el,
-					bottom: `-${this.$el.offsetHeight}px`,
+					[this.pos]: `-${this.$el.offsetHeight}px`,
 					duration: 500,
 					easing: 'easeOutQuad',
-					complete: () => this.$destroy()
+					complete: () => this.destroyDom()
 				});
 			}, 6000);
 		});
@@ -40,8 +45,7 @@ export default Vue.extend({
 	$height = 78px
 
 	position fixed
-	z-index 1024
-	bottom -($height)
+	z-index 10000
 	left 0
 	right 0
 	width 100%
@@ -52,6 +56,12 @@ export default Vue.extend({
 	pointer-events none
 	font-size 80%
 
+	&.bottom
+		bottom -($height)
+
+	&.top
+		top -($height)
+
 	> div
 		height 100%
 		-webkit-backdrop-filter blur(2px)
diff --git a/src/client/app/mobile/views/components/post-form-dialog.vue b/src/client/app/mobile/views/components/post-form-dialog.vue
index 6fe9249321..15b36db945 100644
--- a/src/client/app/mobile/views/components/post-form-dialog.vue
+++ b/src/client/app/mobile/views/components/post-form-dialog.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
-	<div class="bg" ref="bg" @click="onBgClick"></div>
-	<div class="main" ref="main" @click.self="onBgClick">
+	<div class="bg" ref="bg"></div>
+	<div class="main" ref="main">
 		<mk-post-form ref="form"
 			:reply="reply"
 			:renote="renote"
@@ -79,15 +79,10 @@ export default Vue.extend({
 				translateY: 16,
 				duration: 300,
 				easing: 'easeOutQuad',
-				complete: () => this.$destroy()
+				complete: () => this.destroyDom()
 			});
 		},
 
-		onBgClick() {
-			this.$emit('cancel');
-			this.close();
-		},
-
 		onPosted() {
 			this.$emit('posted');
 			this.close();
diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue
index 8b1f7b08c8..e53ba48ffb 100644
--- a/src/client/app/mobile/views/components/post-form.vue
+++ b/src/client/app/mobile/views/components/post-form.vue
@@ -4,14 +4,14 @@
 		<header>
 			<button class="cancel" @click="cancel">%fa:times%</button>
 			<div>
-				<span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span>
+				<span class="text-count" :class="{ over: trimmedLength(text) > 1000 }">{{ 1000 - trimmedLength(text) }}</span>
 				<span class="geo" v-if="geo">%fa:map-marker-alt%</span>
 				<button class="submit" :disabled="!canPost" @click="post">{{ submitText }}</button>
 			</div>
 		</header>
 		<div class="form">
-			<mk-note-preview v-if="reply" :note="reply"/>
-			<mk-note-preview v-if="renote" :note="renote"/>
+			<mk-note-preview class="preview" v-if="reply" :note="reply"/>
+			<mk-note-preview class="preview" v-if="renote" :note="renote"/>
 			<div v-if="visibility == 'specified'" class="visibleUsers">
 				<span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span>
 				<a @click="addVisibleUser">+%i18n:@add-visible-user%</a>
@@ -59,6 +59,9 @@ import MkVisibilityChooser from '../../../common/views/components/visibility-cho
 import getFace from '../../../common/scripts/get-face';
 import parse from '../../../../../mfm/parse';
 import { host } from '../../../config';
+import { erase } from '../../../../../prelude/array';
+import { length } from 'stringz';
+import parseAcct from '../../../../../misc/acct/parse';
 
 export default Vue.extend({
 	components: {
@@ -94,7 +97,7 @@ export default Vue.extend({
 			files: [],
 			poll: false,
 			geo: null,
-			visibility: this.$store.state.device.visibility || 'public',
+			visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
 			visibleUsers: [],
 			useCw: false,
 			cw: null,
@@ -178,6 +181,10 @@ export default Vue.extend({
 	},
 
 	methods: {
+		trimmedLength(text: string) {
+			return length(text.trim());
+		},
+
 		addTag(tag: string) {
 			insertTextAtCursor(this.$refs.text, ` #${tag} `);
 		},
@@ -200,12 +207,12 @@ export default Vue.extend({
 
 		attachMedia(driveFile) {
 			this.files.push(driveFile);
-			this.$emit('change-attached-media', this.files);
+			this.$emit('change-attached-files', this.files);
 		},
 
 		detachMedia(file) {
 			this.files = this.files.filter(x => x.id != file.id);
-			this.$emit('change-attached-media', this.files);
+			this.$emit('change-attached-files', this.files);
 		},
 
 		onChangeFile() {
@@ -252,24 +259,23 @@ export default Vue.extend({
 		addVisibleUser() {
 			(this as any).apis.input({
 				title: '%i18n:@username-prompt%'
-			}).then(username => {
-				(this as any).api('users/show', {
-					username
-				}).then(user => {
+			}).then(acct => {
+				if (acct.startsWith('@')) acct = acct.substr(1);
+				(this as any).api('users/show', parseAcct(acct)).then(user => {
 					this.visibleUsers.push(user);
 				});
 			});
 		},
 
 		removeVisibleUser(user) {
-			this.visibleUsers = this.visibleUsers.filter(u => u != user);
+			this.visibleUsers = erase(user, this.visibleUsers);
 		},
 
 		clear() {
 			this.text = '';
 			this.files = [];
 			this.poll = false;
-			this.$emit('change-attached-media');
+			this.$emit('change-attached-files');
 		},
 
 		post() {
@@ -277,7 +283,7 @@ export default Vue.extend({
 			const viaMobile = this.$store.state.settings.disableViaMobile !== true;
 			(this as any).api('notes/create', {
 				text: this.text == '' ? undefined : this.text,
-				mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
+				fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
 				replyId: this.reply ? this.reply.id : undefined,
 				renoteId: this.renote ? this.renote.id : undefined,
 				poll: this.poll ? (this.$refs.poll as any).get() : undefined,
@@ -302,7 +308,7 @@ export default Vue.extend({
 			if (this.text && this.text != '') {
 				const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
-				localStorage.setItem('hashtags', JSON.stringify(hashtags.concat(history).reduce((a, c) => a.includes(c) ? a : [...a, c], [])));
+				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
 			}
 		},
 
@@ -381,7 +387,7 @@ root(isDark)
 			max-width 500px
 			margin 0 auto
 
-			> .mk-note-preview
+			> .preview
 				padding 16px
 
 			> .visibleUsers
diff --git a/src/client/app/mobile/views/components/sub-note-content.vue b/src/client/app/mobile/views/components/sub-note-content.vue
index a4ce49786e..4d0aa25f34 100644
--- a/src/client/app/mobile/views/components/sub-note-content.vue
+++ b/src/client/app/mobile/views/components/sub-note-content.vue
@@ -7,9 +7,9 @@
 		<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
 		<a class="rp" v-if="note.renoteId">RP: ...</a>
 	</div>
-	<details v-if="note.media.length > 0">
-		<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary>
-		<mk-media-list :media-list="note.media"/>
+	<details v-if="note.files.length > 0">
+		<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>
+		<mk-media-list :media-list="note.files"/>
 	</details>
 	<details v-if="note.poll">
 		<summary>%i18n:@poll%</summary>
diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue
index 54eed1b6d4..c3ae05fef6 100644
--- a/src/client/app/mobile/views/components/ui.nav.vue
+++ b/src/client/app/mobile/views/components/ui.nav.vue
@@ -34,6 +34,12 @@
 					<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
 				</ul>
 			</div>
+			<div class="announcements" v-if="announcements && announcements.length > 0">
+				<article v-for="announcement in announcements">
+					<span v-html="announcement.title" class="title"></span>
+					<div v-html="announcement.text"></div>
+				</article>
+			</div>
 			<a :href="aboutUrl"><p class="about">%i18n:@about%</p></a>
 		</div>
 	</transition>
@@ -46,23 +52,32 @@ import { lang } from '../../../config';
 
 export default Vue.extend({
 	props: ['isOpen'],
+
 	data() {
 		return {
 			hasGameInvitation: false,
 			connection: null,
 			connectionId: null,
-			aboutUrl: `/docs/${lang}/about`
+			aboutUrl: `/docs/${lang}/about`,
+			announcements: []
 		};
 	},
+
 	computed: {
 		hasUnreadNotification(): boolean {
 			return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification;
 		},
+
 		hasUnreadMessagingMessage(): boolean {
 			return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
 		}
 	},
+
 	mounted() {
+		(this as any).os.getMeta().then(meta => {
+			this.announcements = meta.broadcasts;
+		});
+
 		if (this.$store.getters.isSignedIn) {
 			this.connection = (this as any).os.stream.getConnection();
 			this.connectionId = (this as any).os.stream.use();
@@ -71,6 +86,7 @@ export default Vue.extend({
 			this.connection.on('reversi_no_invites', this.onReversiNoInvites);
 		}
 	},
+
 	beforeDestroy() {
 		if (this.$store.getters.isSignedIn) {
 			this.connection.off('reversi_invited', this.onReversiInvited);
@@ -78,18 +94,22 @@ export default Vue.extend({
 			(this as any).os.stream.dispose(this.connectionId);
 		}
 	},
+
 	methods: {
 		search() {
 			const query = window.prompt('%i18n:@search%');
 			if (query == null || query == '') return;
 			this.$router.push(`/search?q=${encodeURIComponent(query)}`);
 		},
+
 		onReversiInvited() {
 			this.hasGameInvitation = true;
 		},
+
 		onReversiNoInvites() {
 			this.hasGameInvitation = false;
 		},
+
 		dark() {
 			this.$store.commit('device/set', {
 				key: 'darkmode',
@@ -204,6 +224,17 @@ root(isDark)
 					color $color
 					opacity 0.5
 
+	.announcements
+		> article
+			background isDark ? rgba(30, 129, 216, 0.2) : rgba(155, 196, 232, 0.2)
+			color isDark ? #fff : #3f4967
+			padding 16px
+			margin 8px 0
+			font-size 12px
+
+			> .title
+				font-weight bold
+
 	.about
 		margin 0 0 8px 0
 		padding 1em 0
diff --git a/src/client/app/mobile/views/components/user-timeline.vue b/src/client/app/mobile/views/components/user-timeline.vue
index 6be675c0a7..7cd23d6655 100644
--- a/src/client/app/mobile/views/components/user-timeline.vue
+++ b/src/client/app/mobile/views/components/user-timeline.vue
@@ -41,7 +41,7 @@ export default Vue.extend({
 			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 				(this as any).api('users/notes', {
 					userId: this.user.id,
-					withMedia: this.withMedia,
+					withFiles: this.withMedia,
 					limit: fetchLimit + 1
 				}).then(notes => {
 					if (notes.length == fetchLimit + 1) {
@@ -62,7 +62,7 @@ export default Vue.extend({
 
 			const promise = (this as any).api('users/notes', {
 				userId: this.user.id,
-				withMedia: this.withMedia,
+				withFiles: this.withMedia,
 				limit: fetchLimit + 1,
 				untilId: (this.$refs.timeline as any).tail().id
 			});
diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue
index 706c9cd28b..333ca1a7a1 100644
--- a/src/client/app/mobile/views/pages/home.vue
+++ b/src/client/app/mobile/views/pages/home.vue
@@ -24,8 +24,8 @@
 			<div class="body">
 				<div>
 					<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
-					<span :data-active="src == 'local'" @click="src = 'local'">%fa:R comments% %i18n:@local%</span>
-					<span :data-active="src == 'hybrid'" @click="src = 'hybrid'">%fa:share-alt% %i18n:@hybrid%</span>
+					<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
+					<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
 					<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
 					<template v-if="lists">
 						<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
@@ -60,7 +60,8 @@ export default Vue.extend({
 			src: 'home',
 			list: null,
 			lists: null,
-			showNav: false
+			showNav: false,
+			enableLocalTimeline: false
 		};
 	},
 
@@ -85,6 +86,10 @@ export default Vue.extend({
 	},
 
 	created() {
+		(this as any).os.getMeta().then(meta => {
+			this.enableLocalTimeline = !meta.disableLocalTimeline;
+		});
+
 		if (this.$store.state.device.tl) {
 			this.src = this.$store.state.device.tl.src;
 			if (this.src == 'list') {
diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue
index 838ffd2a6b..9b0e521a4f 100644
--- a/src/client/app/mobile/views/pages/settings.vue
+++ b/src/client/app/mobile/views/pages/settings.vue
@@ -10,80 +10,119 @@
 			<ui-card>
 				<div slot="title">%fa:palette% %i18n:@design%</div>
 
-				<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
-				<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
-				<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
-				<ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
-				<ui-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
-				<ui-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
+				<section>
+					<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
+					<ui-switch v-model="circleIcons">%i18n:@circle-icons%</ui-switch>
+					<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch>
+					<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch>
+					<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch>
+					<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
+					<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch>
+					<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
+					<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
+				</section>
 
-				<div>
-					<div>%i18n:@timeline%</div>
-					<ui-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</ui-switch>
-					<ui-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</ui-switch>
-					<ui-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
-					<ui-switch v-model="$store.state.settings.showLocalRenotes" @change="onChangeShowLocalRenotes">%i18n:@show-local-renotes%</ui-switch>
-				</div>
+				<section>
+					<header>%i18n:@timeline%</header>
+					<div>
+						<ui-switch v-model="showReplyTarget">%i18n:@show-reply-target%</ui-switch>
+						<ui-switch v-model="showMyRenotes">%i18n:@show-my-renotes%</ui-switch>
+						<ui-switch v-model="showRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
+						<ui-switch v-model="showLocalRenotes">%i18n:@show-local-renotes%</ui-switch>
+					</div>
+				</section>
 
-				<div>
-					<div>%i18n:@post-style%</div>
+				<section>
+					<header>%i18n:@post-style%</header>
 					<ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio>
 					<ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio>
-				</div>
+				</section>
+
+				<section>
+					<header>%i18n:@notification-position%</header>
+					<ui-radio v-model="mobileNotificationPosition" value="bottom">%i18n:@notification-position-bottom%</ui-radio>
+					<ui-radio v-model="mobileNotificationPosition" value="top">%i18n:@notification-position-top%</ui-radio>
+				</section>
 			</ui-card>
 
 			<ui-card>
 				<div slot="title">%fa:cog% %i18n:@behavior%</div>
-				<ui-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
-				<ui-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
-				<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
-				<ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
-				<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
+
+				<section>
+					<ui-switch v-model="fetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
+					<ui-switch v-model="disableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
+					<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
+					<ui-switch v-model="loadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
+					<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
+				</section>
+
+				<section>
+					<header>%i18n:@note-visibility%</header>
+					<ui-switch v-model="rememberNoteVisibility">%i18n:@remember-note-visibility%</ui-switch>
+					<section>
+						<header>%i18n:@default-note-visibility%</header>
+						<ui-select v-model="defaultNoteVisibility">
+							<option value="public">%i18n:common.note-visibility.public%</option>
+							<option value="home">%i18n:common.note-visibility.home%</option>
+							<option value="followers">%i18n:common.note-visibility.followers%</option>
+							<option value="specified">%i18n:common.note-visibility.specified%</option>
+							<option value="private">%i18n:common.note-visibility.private%</option>
+						</ui-select>
+					</section>
+				</section>
 			</ui-card>
 
 			<ui-card>
 				<div slot="title">%fa:volume-up% %i18n:@sound%</div>
 
-				<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch>
+				<section>
+					<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch>
+				</section>
 			</ui-card>
 
 			<ui-card>
 				<div slot="title">%fa:language% %i18n:@lang%</div>
 
-				<ui-select v-model="lang" placeholder="%i18n:@auto%">
-					<optgroup label="%i18n:@recommended%">
-						<option value="">%i18n:@auto%</option>
-					</optgroup>
+				<section class="fit-top">
+					<ui-select v-model="lang" placeholder="%i18n:@auto%">
+						<optgroup label="%i18n:@recommended%">
+							<option value="">%i18n:@auto%</option>
+						</optgroup>
 
-					<optgroup label="%i18n:@specify-language%">
-						<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
-					</optgroup>
-				</ui-select>
-				<span>%fa:info-circle% %i18n:@lang-tip%</span>
+						<optgroup label="%i18n:@specify-language%">
+							<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
+						</optgroup>
+					</ui-select>
+					<span>%fa:info-circle% %i18n:@lang-tip%</span>
+				</section>
 			</ui-card>
 
 			<ui-card>
 				<div slot="title">%fa:B twitter% %i18n:@twitter%</div>
 
-				<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
-				<p>
-					<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
-					<span v-if="$store.state.i.twitter"> or </span>
-					<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
-				</p>
+				<section>
+					<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
+					<p>
+						<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
+						<span v-if="$store.state.i.twitter"> or </span>
+						<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
+					</p>
+				</section>
 			</ui-card>
 
 			<ui-card>
 				<div slot="title">%fa:sync-alt% %i18n:@update%</div>
 
-				<div>%i18n:@version% <i>{{ version }}</i></div>
-				<template v-if="latestVersion !== undefined">
-					<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
-				</template>
-				<ui-button @click="checkForUpdate" :disabled="checkingForUpdate">
-					<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
-					<template v-else>%i18n:@check-for-updates%</template>
-				</ui-button>
+				<section>
+					<div>%i18n:@version% <i>{{ version }}</i></div>
+					<template v-if="latestVersion !== undefined">
+						<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
+					</template>
+					<ui-button @click="checkForUpdate" :disabled="checkingForUpdate">
+						<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
+						<template v-else>%i18n:@check-for-updates%</template>
+					</ui-button>
+				</section>
 			</ui-card>
 		</div>
 
@@ -129,11 +168,21 @@ export default Vue.extend({
 			set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
 		},
 
+		alwaysShowNsfw: {
+			get() { return this.$store.state.device.alwaysShowNsfw; },
+			set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); }
+		},
+
 		postStyle: {
 			get() { return this.$store.state.device.postStyle; },
 			set(value) { this.$store.commit('device/set', { key: 'postStyle', value }); }
 		},
 
+		mobileNotificationPosition: {
+			get() { return this.$store.state.device.mobileNotificationPosition; },
+			set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); }
+		},
+
 		lightmode: {
 			get() { return this.$store.state.device.lightmode; },
 			set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); }
@@ -153,6 +202,86 @@ export default Vue.extend({
 			get() { return this.$store.state.device.enableSounds; },
 			set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
 		},
+
+		fetchOnScroll: {
+			get() { return this.$store.state.settings.fetchOnScroll; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); }
+		},
+
+		rememberNoteVisibility: {
+			get() { return this.$store.state.settings.rememberNoteVisibility; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); }
+		},
+
+		disableViaMobile: {
+			get() { return this.$store.state.settings.disableViaMobile; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
+		},
+
+		loadRemoteMedia: {
+			get() { return this.$store.state.settings.loadRemoteMedia; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'loadRemoteMedia', value }); }
+		},
+
+		circleIcons: {
+			get() { return this.$store.state.settings.circleIcons; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'circleIcons', value }); }
+		},
+
+		contrastedAcct: {
+			get() { return this.$store.state.settings.contrastedAcct; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'contrastedAcct', value }); }
+		},
+
+		showFullAcct: {
+			get() { return this.$store.state.settings.showFullAcct; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showFullAcct', value }); }
+		},
+
+		iLikeSushi: {
+			get() { return this.$store.state.settings.iLikeSushi; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); }
+		},
+
+		games_reversi_showBoardLabels: {
+			get() { return this.$store.state.settings.games.reversi.showBoardLabels; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.showBoardLabels', value }); }
+		},
+
+		games_reversi_useContrastStones: {
+			get() { return this.$store.state.settings.games.reversi.useContrastStones; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useContrastStones', value }); }
+		},
+
+		disableAnimatedMfm: {
+			get() { return this.$store.state.settings.disableAnimatedMfm; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'disableAnimatedMfm', value }); }
+		},
+
+		showReplyTarget: {
+			get() { return this.$store.state.settings.showReplyTarget; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showReplyTarget', value }); }
+		},
+
+		showMyRenotes: {
+			get() { return this.$store.state.settings.showMyRenotes; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showMyRenotes', value }); }
+		},
+
+		showRenotedMyNotes: {
+			get() { return this.$store.state.settings.showRenotedMyNotes; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showRenotedMyNotes', value }); }
+		},
+
+		showLocalRenotes: {
+			get() { return this.$store.state.settings.showLocalRenotes; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'showLocalRenotes', value }); }
+		},
+
+		defaultNoteVisibility: {
+			get() { return this.$store.state.settings.defaultNoteVisibility; },
+			set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); }
+		},
 	},
 
 	mounted() {
@@ -164,90 +293,6 @@ export default Vue.extend({
 			(this as any).os.signout();
 		},
 
-		onChangeFetchOnScroll(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'fetchOnScroll',
-				value: v
-			});
-		},
-
-		onChangeDisableViaMobile(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'disableViaMobile',
-				value: v
-			});
-		},
-
-		onChangeLoadRemoteMedia(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'loadRemoteMedia',
-				value: v
-			});
-		},
-
-		onChangeCircleIcons(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'circleIcons',
-				value: v
-			});
-		},
-
-		onChangeILikeSushi(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'iLikeSushi',
-				value: v
-			});
-		},
-
-		onChangeReversiBoardLabels(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'games.reversi.showBoardLabels',
-				value: v
-			});
-		},
-
-		onChangeUseContrastReversiStones(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'games.reversi.useContrastStones',
-				value: v
-			});
-		},
-
-		onChangeDisableAnimatedMfm(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'disableAnimatedMfm',
-				value: v
-			});
-		},
-
-		onChangeShowReplyTarget(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showReplyTarget',
-				value: v
-			});
-		},
-
-		onChangeShowMyRenotes(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showMyRenotes',
-				value: v
-			});
-		},
-
-		onChangeShowRenotedMyNotes(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showRenotedMyNotes',
-				value: v
-			});
-		},
-
-		onChangeShowLocalRenotes(v) {
-			this.$store.dispatch('settings/set', {
-				key: 'showLocalRenotes',
-				value: v
-			});
-		},
-
 		checkForUpdate() {
 			this.checkingForUpdate = true;
 			checkForUpdate((this as any).os, true, true).then(newer => {
@@ -273,7 +318,7 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 root(isDark)
 	margin 0 auto
-	max-width 500px
+	max-width 600px
 	width 100%
 
 	> .signin-as
diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue
index 3b797cdde1..127f531902 100644
--- a/src/client/app/mobile/views/pages/settings/settings.profile.vue
+++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue
@@ -2,47 +2,64 @@
 <ui-card>
 	<div slot="title">%fa:user% %i18n:@title%</div>
 
-	<ui-form :disabled="saving">
-		<ui-input v-model="name" :max="30">
-			<span>%i18n:@name%</span>
-		</ui-input>
+	<section class="fit-top">
+		<ui-form :disabled="saving">
+			<ui-input v-model="name" :max="30">
+				<span>%i18n:@name%</span>
+			</ui-input>
 
-		<ui-input v-model="username" readonly>
-			<span>%i18n:@account%</span>
-			<span slot="prefix">@</span>
-			<span slot="suffix">@{{ host }}</span>
-		</ui-input>
+			<ui-input v-model="username" readonly>
+				<span>%i18n:@account%</span>
+				<span slot="prefix">@</span>
+				<span slot="suffix">@{{ host }}</span>
+			</ui-input>
 
-		<ui-input v-model="location">
-			<span>%i18n:@location%</span>
-			<span slot="prefix">%fa:map-marker-alt%</span>
-		</ui-input>
+			<ui-input v-model="location">
+				<span>%i18n:@location%</span>
+				<span slot="prefix">%fa:map-marker-alt%</span>
+			</ui-input>
 
-		<ui-input v-model="birthday" type="date">
-			<span>%i18n:@birthday%</span>
-			<span slot="prefix">%fa:birthday-cake%</span>
-		</ui-input>
+			<ui-input v-model="birthday" type="date">
+				<span>%i18n:@birthday%</span>
+				<span slot="prefix">%fa:birthday-cake%</span>
+			</ui-input>
 
-		<ui-textarea v-model="description" :max="500">
-			<span>%i18n:@description%</span>
-		</ui-textarea>
+			<ui-textarea v-model="description" :max="500">
+				<span>%i18n:@description%</span>
+			</ui-textarea>
 
-		<ui-input type="file" @change="onAvatarChange">
-			<span>%i18n:@avatar%</span>
-			<span slot="icon">%fa:image%</span>
-			<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
-		</ui-input>
+			<ui-input type="file" @change="onAvatarChange">
+				<span>%i18n:@avatar%</span>
+				<span slot="icon">%fa:image%</span>
+				<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
+			</ui-input>
 
-		<ui-input type="file" @change="onBannerChange">
-			<span>%i18n:@banner%</span>
-			<span slot="icon">%fa:image%</span>
-			<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
-		</ui-input>
+			<ui-input type="file" @change="onBannerChange">
+				<span>%i18n:@banner%</span>
+				<span slot="icon">%fa:image%</span>
+				<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
+			</ui-input>
 
-		<ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch>
+			<ui-button @click="save(true)">%i18n:@save%</ui-button>
+		</ui-form>
+	</section>
 
-		<ui-button @click="save">%i18n:@save%</ui-button>
-	</ui-form>
+	<section>
+		<header>%i18n:@advanced%</header>
+
+		<div>
+			<ui-switch v-model="isCat" @change="save(false)">%i18n:@is-cat%</ui-switch>
+			<ui-switch v-model="alwaysMarkNsfw">%i18n:common.always-mark-nsfw%</ui-switch>
+		</div>
+	</section>
+
+	<section>
+		<header>%i18n:@privacy%</header>
+
+		<div>
+			<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
+		</div>
+	</section>
 </ui-card>
 </template>
 
@@ -62,12 +79,20 @@ export default Vue.extend({
 			avatarId: null,
 			bannerId: null,
 			isCat: false,
+			isLocked: false,
 			saving: false,
 			avatarUploading: false,
 			bannerUploading: false
 		};
 	},
 
+	computed: {
+		alwaysMarkNsfw: {
+			get() { return this.$store.state.i.settings.alwaysMarkNsfw; },
+			set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); }
+		},
+	},
+
 	created() {
 		this.name = this.$store.state.i.name || '';
 		this.username = this.$store.state.i.username;
@@ -77,6 +102,7 @@ export default Vue.extend({
 		this.avatarId = this.$store.state.i.avatarId;
 		this.bannerId = this.$store.state.i.bannerId;
 		this.isCat = this.$store.state.i.isCat;
+		this.isLocked = this.$store.state.i.isLocked;
 	},
 
 	methods: {
@@ -124,7 +150,7 @@ export default Vue.extend({
 				});
 		},
 
-		save() {
+		save(notify) {
 			this.saving = true;
 
 			(this as any).api('i/update', {
@@ -134,7 +160,8 @@ export default Vue.extend({
 				birthday: this.birthday || null,
 				avatarId: this.avatarId,
 				bannerId: this.bannerId,
-				isCat: this.isCat
+				isCat: this.isCat,
+				isLocked: this.isLocked
 			}).then(i => {
 				this.saving = false;
 				this.$store.state.i.avatarId = i.avatarId;
@@ -142,7 +169,9 @@ export default Vue.extend({
 				this.$store.state.i.bannerId = i.bannerId;
 				this.$store.state.i.bannerUrl = i.bannerUrl;
 
-				alert('%i18n:@saved%');
+				if (notify) {
+					alert('%i18n:@saved%');
+				}
 			});
 		}
 	}
diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue
index ddea43c9f2..c1082f31a9 100644
--- a/src/client/app/mobile/views/pages/user.vue
+++ b/src/client/app/mobile/views/pages/user.vue
@@ -16,7 +16,7 @@
 				</div>
 				<div class="title">
 					<h1>{{ user | userName }}</h1>
-					<span class="username"><mk-acct :user="user"/></span>
+					<span class="username"><mk-acct :user="user" :detail="true" /></span>
 					<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
 				</div>
 				<div class="description">
diff --git a/src/client/app/mobile/views/pages/user/home.photos.vue b/src/client/app/mobile/views/pages/user/home.photos.vue
index 73ff1d5173..e9025ec816 100644
--- a/src/client/app/mobile/views/pages/user/home.photos.vue
+++ b/src/client/app/mobile/views/pages/user/home.photos.vue
@@ -26,7 +26,7 @@ export default Vue.extend({
 	mounted() {
 		(this as any).api('users/notes', {
 			userId: this.user.id,
-			withMedia: true,
+			withFiles: true,
 			limit: 6
 		}).then(notes => {
 			notes.forEach(note => {
diff --git a/src/client/app/mobile/views/pages/welcome.vue b/src/client/app/mobile/views/pages/welcome.vue
index 49227790ff..74f43f2c71 100644
--- a/src/client/app/mobile/views/pages/welcome.vue
+++ b/src/client/app/mobile/views/pages/welcome.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="welcome">
+<div class="wgwfgvvimdjvhjfwxropcwksnzftjqes">
 	<div>
 		<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name">
 		<p class="host">{{ host }}</p>
@@ -15,12 +15,53 @@
 			<mk-welcome-timeline/>
 		</div>
 		<div class="hashtags">
-			<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link>
+			<mk-tag-cloud/>
+		</div>
+		<div class="photos">
+			<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
 		</div>
 		<div class="stats" v-if="stats">
 			<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
 			<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
 		</div>
+		<div class="announcements" v-if="announcements && announcements.length > 0">
+			<article v-for="announcement in announcements">
+				<span class="title" v-html="announcement.title"></span>
+				<div v-html="announcement.text"></div>
+			</article>
+		</div>
+		<article class="about-misskey">
+			<h1>%i18n:common.intro.title%</h1>
+			<p v-html="'%i18n:common.intro.about%'"></p>
+			<section>
+				<h2>%i18n:common.intro.features%</h2>
+				<section>
+					<h3>%i18n:common.intro.rich-contents%</h3>
+					<div class="image"><img src="/assets/about/post.png" alt=""></div>
+					<p v-html="'%i18n:common.intro.rich-contents-desc%'"></p>
+				</section>
+				<section>
+					<h3>%i18n:common.intro.reaction%</h3>
+					<div class="image"><img src="/assets/about/reaction.png" alt=""></div>
+					<p v-html="'%i18n:common.intro.reaction-desc%'"></p>
+				</section>
+				<section>
+					<h3>%i18n:common.intro.ui%</h3>
+					<div class="image"><img src="/assets/about/ui.png" alt=""></div>
+					<p v-html="'%i18n:common.intro.ui-desc%'"></p>
+				</section>
+				<section>
+					<h3>%i18n:common.intro.drive%</h3>
+					<div class="image"><img src="/assets/about/drive.png" alt=""></div>
+					<p v-html="'%i18n:common.intro.drive-desc%'"></p>
+				</section>
+			</section>
+			<p v-html="'%i18n:common.intro.outro%'"></p>
+		</article>
+		<div class="info" v-if="meta">
+			<p>Version: <b>{{ meta.version }}</b></p>
+			<p>Maintainer: <b><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></b></p>
+		</div>
 		<footer>
 			<small>{{ copyright }}</small>
 		</footer>
@@ -30,39 +71,53 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import { apiUrl, copyright, host } from '../../../config';
+import { copyright, host } from '../../../config';
+import { concat } from '../../../../../prelude/array';
 
 export default Vue.extend({
 	data() {
 		return {
-			apiUrl,
+			meta: null,
 			copyright,
 			stats: null,
 			host,
 			name: 'Misskey',
 			description: '',
-			tags: []
+			photos: [],
+			announcements: []
 		};
 	},
 	created() {
 		(this as any).os.getMeta().then(meta => {
+			this.meta = meta;
 			this.name = meta.name;
 			this.description = meta.description;
+			this.announcements = meta.broadcasts;
 		});
 
 		(this as any).api('stats').then(stats => {
 			this.stats = stats;
 		});
 
-		(this as any).api('hashtags/trend').then(stats => {
-			this.tags = stats.map(x => x.tag);
+		const image = [
+			'image/jpeg',
+			'image/png',
+			'image/gif'
+		];
+
+		(this as any).api('notes/local-timeline', {
+			fileType: image,
+			limit: 6
+		}).then((notes: any[]) => {
+			const files = concat(notes.map((n: any): any[] => n.files));
+			this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
 		});
 	}
 });
 </script>
 
 <style lang="stylus" scoped>
-.welcome
+root(isDark)
 	text-align center
 	//background #fff
 
@@ -138,12 +193,21 @@ export default Vue.extend({
 				-webkit-overflow-scrolling touch
 
 		> .hashtags
-			padding 16px 0
-			border solid 2px #ddd
-			border-radius 8px
+			padding 0 8px
+			height 200px
 
-			> *
-				margin 0 16px
+		> .photos
+			display grid
+			grid-template-rows 1fr 1fr 1fr
+			grid-template-columns 1fr 1fr
+			gap 8px
+			height 300px
+			margin-top 16px
+
+			> div
+				border-radius 4px
+				background-position center center
+				background-size cover
 
 		> .stats
 			margin 16px 0
@@ -156,6 +220,68 @@ export default Vue.extend({
 			> *
 				margin 0 8px
 
+		> .announcements
+			margin 16px 0
+
+			> article
+				background isDark ? rgba(30, 129, 216, 0.2) : rgba(155, 196, 232, 0.2)
+				border-radius 6px
+				color isDark ? #fff : #3f4967
+				padding 16px
+				margin 8px 0
+				font-size 12px
+
+				> .title
+					font-weight bold
+
+		> .about-misskey
+			margin 16px 0
+			padding 32px
+			font-size 14px
+			background #fff
+			border-radius 6px
+			overflow hidden
+			color #3a3e46
+
+			> h1
+				margin 0
+
+				& + p
+					margin-top 8px
+
+			> p:last-child
+				margin-bottom 0
+
+			> section
+				> h2
+					border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
+
+				> section
+					margin-bottom 16px
+					padding-bottom 16px
+					border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
+
+					> h3
+						margin-bottom 8px
+
+					> p
+						margin-bottom 0
+
+					> .image
+						> img
+							display block
+							width 100%
+							height 120px
+							object-fit cover
+
+		> .info
+			padding 16px 0
+			border solid 2px #ddd
+			border-radius 8px
+
+			> *
+				margin 0 16px
+
 		> footer
 			text-align center
 			color #444
@@ -165,4 +291,10 @@ export default Vue.extend({
 				margin 16px 0 0 0
 				opacity 0.7
 
+.wgwfgvvimdjvhjfwxropcwksnzftjqes[data-darkmode]
+	root(true)
+
+.wgwfgvvimdjvhjfwxropcwksnzftjqes:not([data-darkmode])
+	root(false)
+
 </style>
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index 469563495f..997bddc5cc 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -4,6 +4,7 @@ import * as nestedProperty from 'nested-property';
 
 import MiOS from './mios';
 import { hostname } from './config';
+import { erase } from '../../prelude/array';
 
 const defaultSettings = {
 	home: null,
@@ -15,6 +16,8 @@ const defaultSettings = {
 	suggestRecentHashtags: true,
 	showClockOnHeader: true,
 	circleIcons: true,
+	contrastedAcct: true,
+	showFullAcct: false,
 	gradientWindowHeader: false,
 	showReplyTarget: true,
 	showMyRenotes: true,
@@ -24,6 +27,8 @@ const defaultSettings = {
 	disableViaMobile: false,
 	memo: null,
 	iLikeSushi: false,
+	rememberNoteVisibility: false,
+	defaultNoteVisibility: 'public',
 	games: {
 		reversi: {
 			showBoardLabels: false,
@@ -43,7 +48,9 @@ const defaultDeviceSettings = {
 	debug: false,
 	lightmode: false,
 	loadRawImages: false,
-	postStyle: 'standard'
+	alwaysShowNsfw: false,
+	postStyle: 'standard',
+	mobileNotificationPosition: 'bottom'
 };
 
 export default (os: MiOS) => new Vuex.Store({
@@ -194,7 +201,7 @@ export default (os: MiOS) => new Vuex.Store({
 
 				removeDeckColumn(state, id) {
 					state.deck.columns = state.deck.columns.filter(c => c.id != id);
-					state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
+					state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
 					state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
 				},
 
@@ -265,7 +272,7 @@ export default (os: MiOS) => new Vuex.Store({
 
 				stackLeftDeckColumn(state, id) {
 					const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
-					state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
+					state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
 					const left = state.deck.layout[i - 1];
 					if (left) state.deck.layout[i - 1].push(id);
 					state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
@@ -273,7 +280,7 @@ export default (os: MiOS) => new Vuex.Store({
 
 				popRightDeckColumn(state, id) {
 					const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
-					state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
+					state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
 					state.deck.layout.splice(i + 1, 0, [id]);
 					state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
 				},
diff --git a/src/client/app/sw.js b/src/client/app/sw.js
index ac7ea20acf..d381bfb7a5 100644
--- a/src/client/app/sw.js
+++ b/src/client/app/sw.js
@@ -3,6 +3,7 @@
  */
 
 import composeNotification from './common/scripts/compose-notification';
+import { erase } from '../../prelude/array';
 
 // キャッシュするリソース
 const cachee = [
@@ -24,8 +25,7 @@ self.addEventListener('activate', ev => {
 	// Clean up old caches
 	ev.waitUntil(
 		caches.keys().then(keys => Promise.all(
-			keys
-				.filter(key => key != _VERSION_)
+			erase(_VERSION_, keys)
 				.map(key => caches.delete(key))
 		))
 	);
diff --git a/src/client/assets/pointer.png b/src/client/assets/pointer.png
index 0d03f75d2b..c9aaada5a3 100644
Binary files a/src/client/assets/pointer.png and b/src/client/assets/pointer.png differ
diff --git a/src/client/assets/reactions/angry.png b/src/client/assets/reactions/angry.png
deleted file mode 100644
index 7e32dd6809..0000000000
Binary files a/src/client/assets/reactions/angry.png and /dev/null differ
diff --git a/src/client/assets/reactions/confused.png b/src/client/assets/reactions/confused.png
deleted file mode 100644
index c791854183..0000000000
Binary files a/src/client/assets/reactions/confused.png and /dev/null differ
diff --git a/src/client/assets/reactions/congrats.png b/src/client/assets/reactions/congrats.png
deleted file mode 100644
index fdea27fcb9..0000000000
Binary files a/src/client/assets/reactions/congrats.png and /dev/null differ
diff --git a/src/client/assets/reactions/hmm.png b/src/client/assets/reactions/hmm.png
deleted file mode 100644
index 725fe3898d..0000000000
Binary files a/src/client/assets/reactions/hmm.png and /dev/null differ
diff --git a/src/client/assets/reactions/laugh.png b/src/client/assets/reactions/laugh.png
deleted file mode 100644
index 3b3c10a27a..0000000000
Binary files a/src/client/assets/reactions/laugh.png and /dev/null differ
diff --git a/src/client/assets/reactions/like.png b/src/client/assets/reactions/like.png
deleted file mode 100644
index 526b391f96..0000000000
Binary files a/src/client/assets/reactions/like.png and /dev/null differ
diff --git a/src/client/assets/reactions/love.png b/src/client/assets/reactions/love.png
deleted file mode 100644
index 9fe82cd070..0000000000
Binary files a/src/client/assets/reactions/love.png and /dev/null differ
diff --git a/src/client/assets/reactions/pudding.png b/src/client/assets/reactions/pudding.png
deleted file mode 100644
index e4d10a229d..0000000000
Binary files a/src/client/assets/reactions/pudding.png and /dev/null differ
diff --git a/src/client/assets/reactions/rip.png b/src/client/assets/reactions/rip.png
deleted file mode 100644
index 4800fdb91b..0000000000
Binary files a/src/client/assets/reactions/rip.png and /dev/null differ
diff --git a/src/client/assets/reactions/surprise.png b/src/client/assets/reactions/surprise.png
deleted file mode 100644
index aa55592ded..0000000000
Binary files a/src/client/assets/reactions/surprise.png and /dev/null differ
diff --git a/src/client/assets/reactions/sushi.png b/src/client/assets/reactions/sushi.png
deleted file mode 100644
index c30d44eb15..0000000000
Binary files a/src/client/assets/reactions/sushi.png and /dev/null differ
diff --git a/src/client/element.scss b/src/client/element.scss
deleted file mode 100644
index 917198e024..0000000000
--- a/src/client/element.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-/* Element variable definitons */
-/* SEE: http://element.eleme.io/#/en-US/component/custom-theme */
-
-@import '../const.json';
-
-/* theme color */
-$--color-primary: $themeColor;
-
-/* icon font path, required */
-$--font-path: '~element-ui/lib/theme-chalk/fonts';
-
-@import "~element-ui/packages/theme-chalk/src/index";
diff --git a/src/const.json b/src/const.json
index b93226b2d2..af9a22bce8 100644
--- a/src/const.json
+++ b/src/const.json
@@ -1,5 +1,5 @@
 {
 	"copyright": "Copyright (c) 2014-2018 syuilo",
-	"themeColor": "#f6584f",
+	"themeColor": "#fb4e4e",
 	"themeColorForeground": "#fff"
 }
diff --git a/src/db/elasticsearch.ts b/src/db/elasticsearch.ts
index 4acff40793..ee5769d1d4 100644
--- a/src/db/elasticsearch.ts
+++ b/src/db/elasticsearch.ts
@@ -4,6 +4,12 @@ import config from '../config';
 const index = {
 	settings: {
 		analysis: {
+			normalizer: {
+				lowercase_normalizer: {
+					type: 'custom',
+					filter: ['lowercase']
+				}
+			},
 			analyzer: {
 				bigram: {
 					tokenizer: 'bigram_tokenizer'
@@ -24,7 +30,8 @@ const index = {
 				text: {
 					type: 'text',
 					index: true,
-					analyzer: 'bigram'
+					analyzer: 'bigram',
+					normalizer: 'lowercase_normalizer'
 				}
 			}
 		}
diff --git a/src/docs/api/entities/note.yaml b/src/docs/api/entities/note.yaml
index cae9a53f82..6654be2b02 100644
--- a/src/docs/api/entities/note.yaml
+++ b/src/docs/api/entities/note.yaml
@@ -33,19 +33,19 @@ props:
       ja-JP: "投稿の本文"
       en-US: "The text of this note"
 
-  mediaIds:
+  fileIds:
     type: "id(DriveFile)[]"
     optional: true
     desc:
-      ja-JP: "添付されているメディアのID (なければレスポンスでは空配列)"
-      en-US: "The IDs of the attached media (empty array for response if no media is attached)"
+      ja-JP: "添付されているファイルのID (なければレスポンスでは空配列)"
+      en-US: "The IDs of the attached files (empty array for response if no files is attached)"
 
-  media:
+  files:
     type: "entity(DriveFile)[]"
     optional: true
     desc:
-      ja-JP: "添付されているメディア"
-      en-US: "The attached media"
+      ja-JP: "添付されているファイル"
+      en-US: "The attached files"
 
   userId:
     type: "id(User)"
diff --git a/src/games/reversi/core.ts b/src/games/reversi/core.ts
index b610d46884..e724917fbf 100644
--- a/src/games/reversi/core.ts
+++ b/src/games/reversi/core.ts
@@ -1,3 +1,5 @@
+import { count, concat } from "../../prelude/array";
+
 // MISSKEY REVERSI ENGINE
 
 /**
@@ -88,8 +90,8 @@ export default class Reversi {
 		//#endregion
 
 		// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
-		if (this.canPutSomewhere(BLACK).length == 0) {
-			if (this.canPutSomewhere(WHITE).length == 0) {
+		if (!this.canPutSomewhere(BLACK)) {
+			if (!this.canPutSomewhere(WHITE)) {
 				this.turn = null;
 			} else {
 				this.turn = WHITE;
@@ -101,14 +103,14 @@ export default class Reversi {
 	 * 黒石の数
 	 */
 	public get blackCount() {
-		return this.board.filter(x => x === BLACK).length;
+		return count(BLACK, this.board);
 	}
 
 	/**
 	 * 白石の数
 	 */
 	public get whiteCount() {
-		return this.board.filter(x => x === WHITE).length;
+		return count(WHITE, this.board);
 	}
 
 	/**
@@ -170,9 +172,9 @@ export default class Reversi {
 
 	private calcTurn() {
 		// ターン計算
-		if (this.canPutSomewhere(!this.prevColor).length > 0) {
+		if (this.canPutSomewhere(!this.prevColor)) {
 			this.turn = !this.prevColor;
-		} else if (this.canPutSomewhere(this.prevColor).length > 0) {
+		} else if (this.canPutSomewhere(this.prevColor)) {
 			this.turn = this.prevColor;
 		} else {
 			this.turn = null;
@@ -204,10 +206,17 @@ export default class Reversi {
 	/**
 	 * 打つことができる場所を取得します
 	 */
-	public canPutSomewhere(color: Color): number[] {
+	public puttablePlaces(color: Color): number[] {
 		return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
 	}
 
+	/**
+	 * 打つことができる場所があるかどうかを取得します
+	 */
+	public canPutSomewhere(color: Color): boolean {
+		return this.puttablePlaces(color).length > 0;
+	}
+
 	/**
 	 * 指定のマスに石を打つことができるかどうかを取得します
 	 * @param color 自分の色
@@ -229,87 +238,55 @@ export default class Reversi {
 	/**
 	 * 指定のマスに石を置いた時の、反転させられる石を取得します
 	 * @param color 自分の色
-	 * @param pos 位置
+	 * @param initPos 位置
 	 */
-	public effects(color: Color, pos: number): number[] {
+	public effects(color: Color, initPos: number): number[] {
 		const enemyColor = !color;
 
-		// ひっくり返せる石(の位置)リスト
-		let stones: number[] = [];
+		const diffVectors: [number, number][] = [
+			[  0,  -1], // 上
+			[ +1,  -1], // 右上
+			[ +1,   0], // 右
+			[ +1,  +1], // 右下
+			[  0,  +1], // 下
+			[ -1,  +1], // 左下
+			[ -1,   0], // 左
+			[ -1,  -1]  // 左上
+		];
 
-		const initPos = pos;
-
-		// 走査
-		const iterate = (fn: (i: number) => number[]) => {
-			let i = 1;
-			const found = [];
+		const effectsInLine = ([dx, dy]: [number, number]): number[] => {
+			const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
 
+			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
+			let [x, y] = this.transformPosToXy(initPos);
 			while (true) {
-				let [x, y] = fn(i);
+				[x, y] = nextPos(x, y);
 
 				// 座標が指し示す位置がボード外に出たとき
 				if (this.opts.loopedBoard) {
-					if (x <  0             ) x = this.mapWidth  - ((-x) % this.mapWidth);
-					if (y <  0             ) y = this.mapHeight - ((-y) % this.mapHeight);
-					if (x >= this.mapWidth ) x = x % this.mapWidth;
-					if (y >= this.mapHeight) y = y % this.mapHeight;
+					x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth;
+					y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight;
 
-					// for debug
-					//if (x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight) {
-					//	console.log(x, y);
-					//}
-
-					// 一周して自分に帰ってきたら
 					if (this.transformXyToPos(x, y) == initPos) {
-						// ↓のコメントアウトを外すと、「現時点で自分の石が隣接していないが、
-						// そこに置いたとするとループして最終的に挟んだことになる」というケースを有効化します。(Test4のマップで違いが分かります)
-						// このケースを有効にした方が良いのか無効にした方が良いのか判断がつかなかったためとりあえず無効としておきます
-						// (あと無効な方がゲームとしておもしろそうだった)
-						stones = stones.concat(found);
-						break;
+						// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
+						return found;
 					}
 				} else {
-					if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) break;
+					if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight) {
+						return []; // 挟めないことが確定 (盤面外に到達)
+					}
 				}
 
 				const pos = this.transformXyToPos(x, y);
-
-				//#region 「配置不能」マスに当たった場合走査終了
-				const pixel = this.mapDataGet(pos);
-				if (pixel == 'null') break;
-				//#endregion
-
-				// 石取得
+				if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
 				const stone = this.board[pos];
-
-				// 石が置かれていないマスなら走査終了
-				if (stone === null) break;
-
-				// 相手の石なら「ひっくり返せるかもリスト」に入れておく
-				if (stone === enemyColor) found.push(pos);
-
-				// 自分の石なら「ひっくり返せるかもリスト」を「ひっくり返せるリスト」に入れ、走査終了
-				if (stone === color) {
-					stones = stones.concat(found);
-					break;
-				}
-
-				i++;
+				if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
+				if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
+				if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
 			}
 		};
 
-		const [x, y] = this.transformPosToXy(pos);
-
-		iterate(i => [x    , y - i]); // 上
-		iterate(i => [x + i, y - i]); // 右上
-		iterate(i => [x + i, y    ]); // 右
-		iterate(i => [x + i, y + i]); // 右下
-		iterate(i => [x    , y + i]); // 下
-		iterate(i => [x - i, y + i]); // 左下
-		iterate(i => [x - i, y    ]); // 左
-		iterate(i => [x - i, y - i]); // 左上
-
-		return stones;
+		return concat(diffVectors.map(effectsInLine));
 	}
 
 	/**
diff --git a/src/mfm/html.ts b/src/mfm/html.ts
index 2e38fe10a0..b7fa5b6f03 100644
--- a/src/mfm/html.ts
+++ b/src/mfm/html.ts
@@ -4,10 +4,7 @@ const { JSDOM } = jsdom;
 import config from '../config';
 import { INote } from '../models/note';
 import { TextElement } from './parse';
-
-function intersperse<T>(sep: T, xs: T[]): T[] {
-	return [].concat(...xs.map(x => [sep, x])).slice(1);
-}
+import { intersperse } from '../prelude/array';
 
 const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers: INote['mentionedRemoteUsers']) => void } = {
 	bold({ document }, { bold }) {
diff --git a/src/mfm/parse/core/syntax-highlighter.ts b/src/mfm/parse/core/syntax-highlighter.ts
index 2b13608d2b..83aac89f1b 100644
--- a/src/mfm/parse/core/syntax-highlighter.ts
+++ b/src/mfm/parse/core/syntax-highlighter.ts
@@ -1,3 +1,5 @@
+import { capitalize, toUpperCase } from "../../../prelude/string";
+
 function escape(text: string) {
 	return text
 		.replace(/>/g, '&gt;')
@@ -89,8 +91,8 @@ const _keywords = [
 ];
 
 const keywords = _keywords
-	.concat(_keywords.map(k => k[0].toUpperCase() + k.substr(1)))
-	.concat(_keywords.map(k => k.toUpperCase()))
+	.concat(_keywords.map(capitalize))
+	.concat(_keywords.map(toUpperCase))
 	.sort((a, b) => b.length - a.length);
 
 const symbols = [
diff --git a/src/misc/get-note-summary.ts b/src/misc/get-note-summary.ts
index ec7c74cf9f..3c6f2dd3d6 100644
--- a/src/misc/get-note-summary.ts
+++ b/src/misc/get-note-summary.ts
@@ -16,9 +16,9 @@ const summarize = (note: any): string => {
 	// 本文
 	summary += note.text ? note.text : '';
 
-	// メディアが添付されているとき
-	if (note.media.length != 0) {
-		summary += ` (${note.media.length}つのメディア)`;
+	// ファイルが添付されているとき
+	if (note.files.length != 0) {
+		summary += ` (${note.files.length}つのファイル)`;
 	}
 
 	// 投票が添付されているとき
diff --git a/src/misc/is-quote.ts b/src/misc/is-quote.ts
index 420f03a489..a99b8f6434 100644
--- a/src/misc/is-quote.ts
+++ b/src/misc/is-quote.ts
@@ -1,5 +1,5 @@
 import { INote } from '../models/note';
 
 export default function(note: INote): boolean {
-	return note.renoteId != null && (note.text != null || note.poll != null || (note.mediaIds != null && note.mediaIds.length > 0));
+	return note.renoteId != null && (note.text != null || note.poll != null || (note.fileIds != null && note.fileIds.length > 0));
 }
diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts
index 698ef092a6..215b49b305 100644
--- a/src/models/drive-file.ts
+++ b/src/models/drive-file.ts
@@ -92,7 +92,7 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv
 
 	// このDriveFileを添付しているNoteをすべて削除
 	await Promise.all((
-		await Note.find({ mediaIds: d._id })
+		await Note.find({ fileIds: d._id })
 	).map(x => deleteNote(x)));
 
 	// このDriveFileを添付しているMessagingMessageをすべて削除
diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts
index f46abd506d..d778164de0 100644
--- a/src/models/messaging-message.ts
+++ b/src/models/messaging-message.ts
@@ -4,6 +4,7 @@ import { pack as packUser } from './user';
 import { pack as packFile } from './drive-file';
 import db from '../db/mongodb';
 import MessagingHistory, { deleteMessagingHistory } from './messaging-history';
+import { length } from 'stringz';
 
 const MessagingMessage = db.get<IMessagingMessage>('messagingMessages');
 export default MessagingMessage;
@@ -19,7 +20,7 @@ export interface IMessagingMessage {
 }
 
 export function isValidText(text: string): boolean {
-	return text.length <= 1000 && text.trim() != '';
+	return length(text.trim()) <= 1000 && text.trim() != '';
 }
 
 /**
diff --git a/src/models/meta.ts b/src/models/meta.ts
index aef0163dfe..8ca68416f8 100644
--- a/src/models/meta.ts
+++ b/src/models/meta.ts
@@ -4,12 +4,14 @@ const Meta = db.get<IMeta>('meta');
 export default Meta;
 
 export type IMeta = {
-	broadcasts: any[];
-	stats: {
+	broadcasts?: any[];
+	stats?: {
 		notesCount: number;
 		originalNotesCount: number;
 		usersCount: number;
 		originalUsersCount: number;
 	};
-	disableRegistration: boolean;
+	disableRegistration?: boolean;
+	disableLocalTimeline?: boolean;
+	hidedTags?: string[];
 };
diff --git a/src/models/note.ts b/src/models/note.ts
index 9d2e23d901..6530d0b324 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -2,11 +2,12 @@ import * as mongo from 'mongodb';
 const deepcopy = require('deepcopy');
 import rap from '@prezzemolo/rap';
 import db from '../db/mongodb';
+import { length } from 'stringz';
 import { IUser, pack as packUser } from './user';
 import { pack as packApp } from './app';
 import PollVote, { deletePollVote } from './poll-vote';
 import Reaction, { deleteNoteReaction } from './note-reaction';
-import { pack as packFile } from './drive-file';
+import { pack as packFile, IDriveFile } from './drive-file';
 import NoteWatching, { deleteNoteWatching } from './note-watching';
 import NoteReaction from './note-reaction';
 import Favorite, { deleteFavorite } from './favorite';
@@ -17,24 +18,25 @@ const Note = db.get<INote>('notes');
 Note.createIndex('uri', { sparse: true, unique: true });
 Note.createIndex('userId');
 Note.createIndex('tagsLower');
+Note.createIndex('_files.contentType');
 Note.createIndex({
 	createdAt: -1
 });
 export default Note;
 
 export function isValidText(text: string): boolean {
-	return text.length <= 1000 && text.trim() != '';
+	return length(text.trim()) <= 1000 && text.trim() != '';
 }
 
 export function isValidCw(text: string): boolean {
-	return text.length <= 100;
+	return length(text.trim()) <= 100;
 }
 
 export type INote = {
 	_id: mongo.ObjectID;
 	createdAt: Date;
 	deletedAt: Date;
-	mediaIds: mongo.ObjectID[];
+	fileIds: mongo.ObjectID[];
 	replyId: mongo.ObjectID;
 	renoteId: mongo.ObjectID;
 	poll: {
@@ -92,6 +94,7 @@ export type INote = {
 		inbox?: string;
 	};
 	_replyIds?: mongo.ObjectID[];
+	_files?: IDriveFile[];
 };
 
 /**
@@ -160,6 +163,66 @@ export async function deleteNote(note: string | mongo.ObjectID | INote) {
 	console.log(`Note: deleted ${n._id}`);
 }
 
+export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => {
+	let hide = false;
+
+	// visibility が private かつ投稿者のIDが自分のIDではなかったら非表示
+	if (packedNote.visibility == 'private' && (meId == null || !meId.equals(packedNote.userId))) {
+		hide = true;
+	}
+
+	// visibility が specified かつ自分が指定されていなかったら非表示
+	if (packedNote.visibility == 'specified') {
+		if (meId == null) {
+			hide = true;
+		} else if (meId.equals(packedNote.userId)) {
+			hide = false;
+		} else {
+			// 指定されているかどうか
+			const specified = packedNote.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId));
+
+			if (specified) {
+				hide = false;
+			} else {
+				hide = true;
+			}
+		}
+	}
+
+	// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
+	if (packedNote.visibility == 'followers') {
+		if (meId == null) {
+			hide = true;
+		} else if (meId.equals(packedNote.userId)) {
+			hide = false;
+		} else {
+			// フォロワーかどうか
+			const following = await Following.findOne({
+				followeeId: packedNote.userId,
+				followerId: meId
+			});
+
+			if (following == null) {
+				hide = true;
+			} else {
+				hide = false;
+			}
+		}
+	}
+
+	if (hide) {
+		packedNote.fileIds = [];
+		packedNote.files = [];
+		packedNote.text = null;
+		packedNote.poll = null;
+		packedNote.cw = null;
+		packedNote.tags = [];
+		packedNote.tagsLower = [];
+		packedNote.geo = null;
+		packedNote.isHidden = true;
+	}
+};
+
 /**
  * Pack a note for API response
  *
@@ -172,11 +235,13 @@ export const pack = async (
 	note: string | mongo.ObjectID | INote,
 	me?: string | mongo.ObjectID | IUser,
 	options?: {
-		detail: boolean
+		detail?: boolean;
+		skipHide?: boolean;
 	}
 ) => {
 	const opts = Object.assign({
-		detail: true
+		detail: true,
+		skipHide: false
 	}, options);
 
 	// Me
@@ -205,52 +270,6 @@ export const pack = async (
 
 	if (!_note) throw `invalid note arg ${note}`;
 
-	let hide = false;
-
-	// visibility が private かつ投稿者のIDが自分のIDではなかったら非表示
-	if (_note.visibility == 'private' && (meId == null || !meId.equals(_note.userId))) {
-		hide = true;
-	}
-
-	// visibility が specified かつ自分が指定されていなかったら非表示
-	if (_note.visibility == 'specified') {
-		if (meId == null) {
-			hide = true;
-		} else if (meId.equals(_note.userId)) {
-			hide = false;
-		} else {
-			// 指定されているかどうか
-			const specified = _note.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId));
-
-			if (specified) {
-				hide = false;
-			} else {
-				hide = true;
-			}
-		}
-	}
-
-	// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
-	if (_note.visibility == 'followers') {
-		if (meId == null) {
-			hide = true;
-		} else if (meId.equals(_note.userId)) {
-			hide = false;
-		} else {
-			// フォロワーかどうか
-			const following = await Following.findOne({
-				followeeId: _note.userId,
-				followerId: meId
-			});
-
-			if (following == null) {
-				hide = true;
-			} else {
-				hide = false;
-			}
-		}
-	}
-
 	const id = _note._id;
 
 	// Rename _id to id
@@ -271,11 +290,15 @@ export const pack = async (
 		_note.app = packApp(_note.appId);
 	}
 
-	// Populate media
-	_note.media = hide ? [] : Promise.all(_note.mediaIds.map((fileId: mongo.ObjectID) =>
+	// Populate files
+	_note.files = Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) =>
 		packFile(fileId)
 	));
 
+	// 後方互換性のため
+	_note.mediaIds = _note.fileIds;
+	_note.media = _note.files;
+
 	// When requested a detailed note data
 	if (opts.detail) {
 		//#region 重いので廃止
@@ -298,7 +321,7 @@ export const pack = async (
 		}
 
 		// Poll
-		if (meId && _note.poll && !hide) {
+		if (meId && _note.poll) {
 			_note.poll = (async poll => {
 				const vote = await PollVote
 					.findOne({
@@ -343,15 +366,8 @@ export const pack = async (
 		_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ');
 	}
 
-	if (hide) {
-		_note.mediaIds = [];
-		_note.text = null;
-		_note.poll = null;
-		_note.cw = null;
-		_note.tags = [];
-		_note.tagsLower = [];
-		_note.geo = null;
-		_note.isHidden = true;
+	if (!opts.skipHide) {
+		await hideNote(_note, meId);
 	}
 
 	return _note;
diff --git a/src/models/stats.ts b/src/models/stats.ts
index d496f2c480..c4c838caeb 100644
--- a/src/models/stats.ts
+++ b/src/models/stats.ts
@@ -204,4 +204,30 @@ export interface IStats {
 			decSize: number;
 		};
 	};
+
+	/**
+	 * ネットワークに関する統計
+	 */
+	network: {
+		/**
+		 * サーバーへのリクエスト数
+		 */
+		requests: number;
+
+		/**
+		 * 応答時間の合計
+		 * TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる
+		 */
+		totalTime: number;
+
+		/**
+		 * 合計受信データ量
+		 */
+		incomingBytes: number;
+
+		/**
+		 * 合計送信データ量
+		 */
+		outgoingBytes: number;
+	};
 }
diff --git a/src/models/user.ts b/src/models/user.ts
index 8f3fbbdc8f..64197c91c2 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -102,7 +102,10 @@ export interface ILocalUser extends IUserBase {
 	twoFactorEnabled: boolean;
 	twoFactorTempSecret?: string;
 	clientSettings: any;
-	settings: any;
+	settings: {
+		autoWatch: boolean;
+		alwaysMarkNsfw?: boolean;
+	};
 	hasUnreadNotification: boolean;
 	hasUnreadMessagingMessage: boolean;
 }
diff --git a/src/prelude/README.md b/src/prelude/README.md
new file mode 100644
index 0000000000..bb728cfb1b
--- /dev/null
+++ b/src/prelude/README.md
@@ -0,0 +1,3 @@
+# Prelude
+このディレクトリのコードはJavaScriptの表現能力を補うためのコードです。
+Misskey固有の処理とは独立したコードの集まりですが、Misskeyのコードを読みやすくすることを目的としています。
diff --git a/src/prelude/array.ts b/src/prelude/array.ts
new file mode 100644
index 0000000000..54f7081712
--- /dev/null
+++ b/src/prelude/array.ts
@@ -0,0 +1,27 @@
+export function countIf<T>(f: (x: T) => boolean, xs: T[]): number {
+	return xs.filter(f).length;
+}
+
+export function count<T>(x: T, xs: T[]): number {
+	return countIf(y => x === y, xs);
+}
+
+export function concat<T>(xss: T[][]): T[] {
+	return ([] as T[]).concat(...xss);
+}
+
+export function intersperse<T>(sep: T, xs: T[]): T[] {
+	return concat(xs.map(x => [sep, x])).slice(1);
+}
+
+export function erase<T>(x: T, xs: T[]): T[] {
+	return xs.filter(y => x !== y);
+}
+
+export function unique<T>(xs: T[]): T[] {
+	return [...new Set(xs)];
+}
+
+export function sum(xs: number[]): number {
+	return xs.reduce((a, b) => a + b, 0);
+}
diff --git a/src/prelude/math.ts b/src/prelude/math.ts
new file mode 100644
index 0000000000..07b94bec30
--- /dev/null
+++ b/src/prelude/math.ts
@@ -0,0 +1,3 @@
+export function gcd(a: number, b: number): number {
+	return b === 0 ? a : gcd(b, a % b);
+}
diff --git a/src/prelude/string.ts b/src/prelude/string.ts
new file mode 100644
index 0000000000..cae776bc3d
--- /dev/null
+++ b/src/prelude/string.ts
@@ -0,0 +1,11 @@
+export function capitalize(s: string): string {
+	return toUpperCase(s.charAt(0)) + toLowerCase(s.slice(1));
+}
+
+export function toUpperCase(s: string): string {
+	return s.toUpperCase();
+}
+
+export function toLowerCase(s: string): string {
+	return s.toLowerCase();
+}
diff --git a/src/remote/activitypub/misc/get-note-html.ts b/src/remote/activitypub/misc/get-note-html.ts
index 8df440930b..f92e91c34e 100644
--- a/src/remote/activitypub/misc/get-note-html.ts
+++ b/src/remote/activitypub/misc/get-note-html.ts
@@ -4,9 +4,8 @@ import parse from '../../../mfm/parse';
 import config from '../../../config';
 
 export default function(note: INote) {
-	if (note.text == null) return null;
-
 	let html = toHtml(parse(note.text), note.mentionedRemoteUsers);
+	if (html == null) html = '';
 
 	if (note.poll != null) {
 		const url = `${config.url}/notes/${note._id}`;
diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts
index 1dfeebfdf7..b4afda765a 100644
--- a/src/remote/activitypub/models/note.ts
+++ b/src/remote/activitypub/models/note.ts
@@ -78,11 +78,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 	}
 	//#endergion
 
-	// 添付メディア
+	// 添付ファイル
 	// TODO: attachmentは必ずしもImageではない
 	// TODO: attachmentは必ずしも配列ではない
 	// Noteがsensitiveなら添付もsensitiveにする
-	const media = note.attachment
+	const files = note.attachment
 		.map(attach => attach.sensitive = note.sensitive)
 		? await Promise.all(note.attachment.map(x => resolveImage(actor, x)))
 		: [];
@@ -91,7 +91,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 	const reply = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver) : null;
 
 	// テキストのパース
-	const text = htmlToMFM(note.content);
+	const text = note._misskey_content ? note._misskey_content : htmlToMFM(note.content);
 
 	// ユーザーの情報が古かったらついでに更新しておく
 	if (actor.updatedAt == null || Date.now() - actor.updatedAt.getTime() > 1000 * 60 * 60 * 24) {
@@ -100,7 +100,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
 
 	return await post(actor, {
 		createdAt: new Date(note.published),
-		media,
+		files: files,
 		reply,
 		renote: undefined,
 		cw: note.summary,
diff --git a/src/remote/activitypub/renderer/announce.ts b/src/remote/activitypub/renderer/announce.ts
index f6276ade04..18e23cc336 100644
--- a/src/remote/activitypub/renderer/announce.ts
+++ b/src/remote/activitypub/renderer/announce.ts
@@ -5,7 +5,7 @@ export default (object: any, note: INote) => {
 	const attributedTo = `${config.url}/users/${note.userId}`;
 
 	return {
-		id: `${config.url}/notes/${note._id}`,
+		id: `${config.url}/notes/${note._id}/activity`,
 		actor: `${config.url}/users/${note.userId}`,
 		type: 'Announce',
 		published: note.createdAt.toISOString(),
diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts
index 1d169d3088..04e131637a 100644
--- a/src/remote/activitypub/renderer/note.ts
+++ b/src/remote/activitypub/renderer/note.ts
@@ -8,8 +8,8 @@ import User from '../../../models/user';
 import toHtml from '../misc/get-note-html';
 
 export default async function renderNote(note: INote, dive = true): Promise<any> {
-	const promisedFiles: Promise<IDriveFile[]> = note.mediaIds
-		? DriveFile.find({ _id: { $in: note.mediaIds } })
+	const promisedFiles: Promise<IDriveFile[]> = note.fileIds
+		? DriveFile.find({ _id: { $in: note.fileIds } })
 		: Promise.resolve([]);
 
 	let inReplyTo;
@@ -87,6 +87,7 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
 		attributedTo,
 		summary: note.cw,
 		content: toHtml(note),
+		_misskey_content_: note.text,
 		published: note.createdAt.toISOString(),
 		to,
 		cc,
diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts
index 3d40ad48cb..28763d3e83 100644
--- a/src/remote/activitypub/type.ts
+++ b/src/remote/activitypub/type.ts
@@ -40,6 +40,7 @@ export interface IOrderedCollection extends IObject {
 
 export interface INote extends IObject {
 	type: 'Note';
+	_misskey_content: string;
 }
 
 export interface IPerson extends IObject {
diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts
index f04f9e91e9..3d346693d8 100644
--- a/src/server/activitypub.ts
+++ b/src/server/activitypub.ts
@@ -10,7 +10,7 @@ import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
 import renderNote from '../remote/activitypub/renderer/note';
 import renderKey from '../remote/activitypub/renderer/key';
 import renderPerson from '../remote/activitypub/renderer/person';
-import Outbox from './activitypub/outbox';
+import Outbox, { packActivity } from './activitypub/outbox';
 import Followers from './activitypub/followers';
 import Following from './activitypub/following';
 
@@ -77,6 +77,22 @@ router.get('/notes/:note', async (ctx, next) => {
 	setResponseType(ctx);
 });
 
+// note activity
+router.get('/notes/:note/activity', async ctx => {
+	const note = await Note.findOne({
+		_id: new mongo.ObjectID(ctx.params.note),
+		visibility: { $in: ['public', 'home'] }
+	});
+
+	if (note === null) {
+		ctx.status = 404;
+		return;
+	}
+
+	ctx.body = pack(await packActivity(note));
+	setResponseType(ctx);
+});
+
 // outbox
 router.get('/users/:user/outbox', Outbox);
 
diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts
index 37df190880..1d062f61a1 100644
--- a/src/server/activitypub/outbox.ts
+++ b/src/server/activitypub/outbox.ts
@@ -8,8 +8,11 @@ import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-c
 import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
 import { setResponseType } from '../activitypub';
 
-import Note from '../../models/note';
+import Note, { INote } from '../../models/note';
 import renderNote from '../../remote/activitypub/renderer/note';
+import renderCreate from '../../remote/activitypub/renderer/create';
+import renderAnnounce from '../../remote/activitypub/renderer/announce';
+import { countIf } from '../../prelude/array';
 
 export default async (ctx: Router.IRouterContext) => {
 	const userId = new mongo.ObjectID(ctx.params.user);
@@ -25,7 +28,7 @@ export default async (ctx: Router.IRouterContext) => {
 	const page: boolean = ctx.request.query.page === 'true';
 
 	// Validate parameters
-	if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) {
+	if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) {
 		ctx.status = 400;
 		return;
 	}
@@ -52,15 +55,7 @@ export default async (ctx: Router.IRouterContext) => {
 
 		const query = {
 			userId: user._id,
-			$and: [{
-				$or: [ { visibility: 'public' }, { visibility: 'home' } ]
-			}, { // exclude renote, but include quote
-				$or: [{
-					text: { $ne: null }
-				}, {
-					mediaIds: { $ne: [] }
-				}]
-			}]
+			visibility: { $in: ['public', 'home'] }
 		} as any;
 
 		if (sinceId) {
@@ -84,10 +79,10 @@ export default async (ctx: Router.IRouterContext) => {
 
 		if (sinceId) notes.reverse();
 
-		const renderedNotes = await Promise.all(notes.map(note => renderNote(note, false)));
+		const activities = await Promise.all(notes.map(note => packActivity(note)));
 		const rendered = renderOrderedCollectionPage(
 			`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
-			user.notesCount, renderedNotes, partOf,
+			user.notesCount, activities, partOf,
 			notes.length > 0 ? `${partOf}?page=true&since_id=${notes[0]._id}` : null,
 			notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null
 		);
@@ -104,3 +99,16 @@ export default async (ctx: Router.IRouterContext) => {
 		setResponseType(ctx);
 	}
 };
+
+/**
+ * Pack Create<Note> or Announce Activity
+ * @param note Note
+ */
+export async function packActivity(note: INote): Promise<object> {
+	if (note.renoteId && note.text == null && note.poll == null && (note.fileIds == null || note.fileIds.length == 0)) {
+		const renote = await Note.findOne(note.renoteId);
+		return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote._id}`, note);
+	}
+
+	return renderCreate(await renderNote(note, false), note);
+}
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index e9abc11f54..ee79e0a13c 100644
--- a/src/server/api/call.ts
+++ b/src/server/api/call.ts
@@ -25,10 +25,8 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
 		return rej('YOU_ARE_NOT_ADMIN');
 	}
 
-	if (app && ep.meta.kind) {
-		if (!app.permission.some(p => p === ep.meta.kind)) {
-			return rej('PERMISSION_DENIED');
-		}
+	if (app && ep.meta.kind && !app.permission.some(p => p === ep.meta.kind)) {
+		return rej('PERMISSION_DENIED');
 	}
 
 	if (ep.meta.requireCredential && ep.meta.limit) {
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index 10ca15d329..3f5cd56b2f 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -21,7 +21,19 @@ export const meta = {
 			desc: {
 				'ja-JP': '招待制か否か'
 			}
-		})
+		}),
+
+		disableLocalTimeline: $.bool.optional.nullable.note({
+			desc: {
+				'ja-JP': 'ローカルタイムライン(とソーシャルタイムライン)を無効にするか否か'
+			}
+		}),
+
+		hidedTags: $.arr($.str).optional.nullable.note({
+			desc: {
+				'ja-JP': '統計などで無視するハッシュタグ'
+			}
+		}),
 	}
 };
 
@@ -39,6 +51,14 @@ export default (params: any) => new Promise(async (res, rej) => {
 		set.disableRegistration = ps.disableRegistration;
 	}
 
+	if (typeof ps.disableLocalTimeline === 'boolean') {
+		set.disableLocalTimeline = ps.disableLocalTimeline;
+	}
+
+	if (Array.isArray(ps.hidedTags)) {
+		set.hidedTags = ps.hidedTags;
+	}
+
 	await Meta.update({}, {
 		$set: set
 	}, { upsert: true });
diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts
new file mode 100644
index 0000000000..ffeafb2538
--- /dev/null
+++ b/src/server/api/endpoints/aggregation/hashtags.ts
@@ -0,0 +1,66 @@
+import Note from '../../../../models/note';
+import Meta from '../../../../models/meta';
+
+export default () => new Promise(async (res, rej) => {
+	const meta = await Meta.findOne({});
+	const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
+
+	const span = 1000 * 60 * 60 * 24 * 7; // 1週間
+
+	//#region 1. 指定期間の内に投稿されたハッシュタグ(とユーザーのペア)を集計
+	const data = await Note.aggregate([{
+		$match: {
+			createdAt: {
+				$gt: new Date(Date.now() - span)
+			},
+			tagsLower: {
+				$exists: true,
+				$ne: []
+			}
+		}
+	}, {
+		$unwind: '$tagsLower'
+	}, {
+		$group: {
+			_id: { tag: '$tagsLower', userId: '$userId' }
+		}
+	}]) as Array<{
+		_id: {
+			tag: string;
+			userId: any;
+		}
+	}>;
+	//#endregion
+
+	if (data.length == 0) {
+		return res([]);
+	}
+
+	let tags: Array<{
+		name: string;
+		count: number;
+	}> = [];
+
+	// カウント
+	data.map(x => x._id).forEach(x => {
+		// ブラックリストに登録されているタグなら弾く
+		if (hidedTags.includes(x.tag)) return;
+
+		const i = tags.findIndex(tag => tag.name == x.tag);
+		if (i != -1) {
+			tags[i].count++;
+		} else {
+			tags.push({
+				name: x.tag,
+				count: 1
+			});
+		}
+	});
+
+	// タグを人気順に並べ替え
+	tags = tags.sort((a, b) => b.count - a.count);
+
+	tags = tags.slice(0, 30);
+
+	res(tags);
+});
diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts
index 7da970131e..3b1a3b56fc 100644
--- a/src/server/api/endpoints/chart.ts
+++ b/src/server/api/endpoints/chart.ts
@@ -6,6 +6,15 @@ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
 
 function migrateStats(stats: IStats[]) {
 	stats.forEach(stat => {
+		if (stat.network == null) {
+			stat.network = {
+				requests: 0,
+				totalTime: 0,
+				incomingBytes: 0,
+				outgoingBytes: 0
+			};
+		}
+
 		const isOldData =
 			stat.users.local.inc == null ||
 			stat.users.local.dec == null ||
@@ -180,6 +189,12 @@ export default (params: any) => new Promise(async (res, rej) => {
 								decCount: 0,
 								decSize: 0
 							}
+						},
+						network: {
+							requests: 0,
+							totalTime: 0,
+							incomingBytes: 0,
+							outgoingBytes: 0
 						}
 					});
 				} else {
@@ -236,6 +251,12 @@ export default (params: any) => new Promise(async (res, rej) => {
 								decCount: 0,
 								decSize: 0
 							}
+						},
+						network: {
+							requests: 0,
+							totalTime: 0,
+							incomingBytes: 0,
+							outgoingBytes: 0
 						}
 					});
 				}
diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts
index dfbd11d0c2..4b5ffa90e0 100644
--- a/src/server/api/endpoints/drive/files/create.ts
+++ b/src/server/api/endpoints/drive/files/create.ts
@@ -31,8 +31,8 @@ export const meta = {
 			}
 		}),
 
-		isSensitive: $.bool.optional.note({
-			default: false,
+		isSensitive: $.bool.optional.nullable.note({
+			default: null,
 			desc: {
 				'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか',
 				'en-US': 'Whether this media is NSFW'
diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts
index c9bea0e3d2..00aa904f08 100644
--- a/src/server/api/endpoints/following/create.ts
+++ b/src/server/api/endpoints/following/create.ts
@@ -57,7 +57,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
 	}
 
 	// Create following
-	create(follower, followee);
+	await create(follower, followee);
 
 	// Send response
 	res(await pack(followee._id, user));
diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts
index f3b4a73ae8..cdfbf43cd1 100644
--- a/src/server/api/endpoints/following/delete.ts
+++ b/src/server/api/endpoints/following/delete.ts
@@ -57,7 +57,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
 	}
 
 	// Delete following
-	deleteFollowing(follower, followee);
+	await deleteFollowing(follower, followee);
 
 	// Send response
 	res(await pack(followee._id, user));
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index 01dfccc71c..0ec6a4ffec 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -1,4 +1,6 @@
 import Note from '../../../../models/note';
+import { erase } from '../../../../prelude/array';
+import Meta from '../../../../models/meta';
 
 /*
 トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
@@ -16,6 +18,9 @@ const max = 5;
  * Get trends of hashtags
  */
 export default () => new Promise(async (res, rej) => {
+	const meta = await Meta.findOne({});
+	const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
+
 	//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
 	const data = await Note.aggregate([{
 		$match: {
@@ -52,6 +57,9 @@ export default () => new Promise(async (res, rej) => {
 
 	// カウント
 	data.map(x => x._id).forEach(x => {
+		// ブラックリストに登録されているタグなら弾く
+		if (hidedTags.includes(x.tag)) return;
+
 		const i = tags.findIndex(tag => tag.name == x.tag);
 		if (i != -1) {
 			tags[i].count++;
@@ -85,8 +93,7 @@ export default () => new Promise(async (res, rej) => {
 	//#endregion
 
 	// タグを人気順に並べ替え
-	let hots = (await Promise.all(hotsPromises))
-		.filter(x => x != null)
+	let hots = erase(null, await Promise.all(hotsPromises))
 		.sort((a, b) => b.count - a.count)
 		.map(tag => tag.name)
 		.slice(0, max);
diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts
index 585339e249..c1be0b6ebc 100644
--- a/src/server/api/endpoints/i/update.ts
+++ b/src/server/api/endpoints/i/update.ts
@@ -6,6 +6,7 @@ import acceptAllFollowRequests from '../../../../services/following/requests/acc
 import { IApp } from '../../../../models/app';
 import config from '../../../../config';
 import { publishToFollowers } from '../../../../services/i/update';
+import getParams from '../../get-params';
 
 export const meta = {
 	desc: {
@@ -15,75 +16,111 @@ export const meta = {
 
 	requireCredential: true,
 
-	kind: 'account-write'
+	kind: 'account-write',
+
+	params: {
+		name: $.str.optional.nullable.pipe(isValidName).note({
+			desc: {
+				'ja-JP': '名前(ハンドルネームやニックネーム)'
+			}
+		}),
+
+		description: $.str.optional.nullable.pipe(isValidDescription).note({
+			desc: {
+				'ja-JP': 'アカウントの説明や自己紹介'
+			}
+		}),
+
+		location: $.str.optional.nullable.pipe(isValidLocation).note({
+			desc: {
+				'ja-JP': '住んでいる地域、所在'
+			}
+		}),
+
+		birthday: $.str.optional.nullable.pipe(isValidBirthday).note({
+			desc: {
+				'ja-JP': '誕生日 (YYYY-MM-DD形式)'
+			}
+		}),
+
+		avatarId: $.type(ID).optional.nullable.note({
+			desc: {
+				'ja-JP': 'アイコンに設定する画像のドライブファイルID'
+			}
+		}),
+
+		bannerId: $.type(ID).optional.nullable.note({
+			desc: {
+				'ja-JP': 'バナーに設定する画像のドライブファイルID'
+			}
+		}),
+
+		wallpaperId: $.type(ID).optional.nullable.note({
+			desc: {
+				'ja-JP': '壁紙に設定する画像のドライブファイルID'
+			}
+		}),
+
+		isLocked: $.bool.optional.note({
+			desc: {
+				'ja-JP': '鍵アカウントか否か'
+			}
+		}),
+
+		isBot: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'Botか否か'
+			}
+		}),
+
+		isCat: $.bool.optional.note({
+			desc: {
+				'ja-JP': '猫か否か'
+			}
+		}),
+
+		autoWatch: $.bool.optional.note({
+			desc: {
+				'ja-JP': '投稿の自動ウォッチをするか否か'
+			}
+		}),
+
+		alwaysMarkNsfw: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか'
+			}
+		}),
+	}
 };
 
 export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => {
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
+
 	const isSecure = user != null && app == null;
 
 	const updates = {} as any;
 
-	// Get 'name' parameter
-	const [name, nameErr] = $.str.optional.nullable.pipe(isValidName).get(params.name);
-	if (nameErr) return rej('invalid name param');
-	if (name) updates.name = name;
+	if (ps.name !== undefined) updates.name = ps.name;
+	if (ps.description !== undefined) updates.description = ps.description;
+	if (ps.location !== undefined) updates['profile.location'] = ps.location;
+	if (ps.birthday !== undefined) updates['profile.birthday'] = ps.birthday;
+	if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
+	if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
+	if (ps.wallpaperId !== undefined) updates.wallpaperId = ps.wallpaperId;
+	if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
+	if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
+	if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
+	if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch;
+	if (typeof ps.alwaysMarkNsfw == 'boolean') updates['settings.alwaysMarkNsfw'] = ps.alwaysMarkNsfw;
 
-	// Get 'description' parameter
-	const [description, descriptionErr] = $.str.optional.nullable.pipe(isValidDescription).get(params.description);
-	if (descriptionErr) return rej('invalid description param');
-	if (description !== undefined) updates.description = description;
-
-	// Get 'location' parameter
-	const [location, locationErr] = $.str.optional.nullable.pipe(isValidLocation).get(params.location);
-	if (locationErr) return rej('invalid location param');
-	if (location !== undefined) updates['profile.location'] = location;
-
-	// Get 'birthday' parameter
-	const [birthday, birthdayErr] = $.str.optional.nullable.pipe(isValidBirthday).get(params.birthday);
-	if (birthdayErr) return rej('invalid birthday param');
-	if (birthday !== undefined) updates['profile.birthday'] = birthday;
-
-	// Get 'avatarId' parameter
-	const [avatarId, avatarIdErr] = $.type(ID).optional.nullable.get(params.avatarId);
-	if (avatarIdErr) return rej('invalid avatarId param');
-	if (avatarId !== undefined) updates.avatarId = avatarId;
-
-	// Get 'bannerId' parameter
-	const [bannerId, bannerIdErr] = $.type(ID).optional.nullable.get(params.bannerId);
-	if (bannerIdErr) return rej('invalid bannerId param');
-	if (bannerId !== undefined) updates.bannerId = bannerId;
-
-	// Get 'wallpaperId' parameter
-	const [wallpaperId, wallpaperIdErr] = $.type(ID).optional.nullable.get(params.wallpaperId);
-	if (wallpaperIdErr) return rej('invalid wallpaperId param');
-	if (wallpaperId !== undefined) updates.wallpaperId = wallpaperId;
-
-	// Get 'isLocked' parameter
-	const [isLocked, isLockedErr] = $.bool.optional.get(params.isLocked);
-	if (isLockedErr) return rej('invalid isLocked param');
-	if (isLocked != null) updates.isLocked = isLocked;
-
-	// Get 'isBot' parameter
-	const [isBot, isBotErr] = $.bool.optional.get(params.isBot);
-	if (isBotErr) return rej('invalid isBot param');
-	if (isBot != null) updates.isBot = isBot;
-
-	// Get 'isCat' parameter
-	const [isCat, isCatErr] = $.bool.optional.get(params.isCat);
-	if (isCatErr) return rej('invalid isCat param');
-	if (isCat != null) updates.isCat = isCat;
-
-	// Get 'autoWatch' parameter
-	const [autoWatch, autoWatchErr] = $.bool.optional.get(params.autoWatch);
-	if (autoWatchErr) return rej('invalid autoWatch param');
-	if (autoWatch != null) updates['settings.autoWatch'] = autoWatch;
-
-	if (avatarId) {
+	if (ps.avatarId) {
 		const avatar = await DriveFile.findOne({
-			_id: avatarId
+			_id: ps.avatarId
 		});
 
 		if (avatar == null) return rej('avatar not found');
+		if (!avatar.contentType.startsWith('image/')) return rej('avatar not an image');
 
 		updates.avatarUrl = avatar.metadata.thumbnailUrl || avatar.metadata.url || `${config.drive_url}/${avatar._id}`;
 
@@ -92,12 +129,13 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
 		}
 	}
 
-	if (bannerId) {
+	if (ps.bannerId) {
 		const banner = await DriveFile.findOne({
-			_id: bannerId
+			_id: ps.bannerId
 		});
 
 		if (banner == null) return rej('banner not found');
+		if (!banner.contentType.startsWith('image/')) return rej('banner not an image');
 
 		updates.bannerUrl = banner.metadata.url || `${config.drive_url}/${banner._id}`;
 
@@ -106,13 +144,13 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
 		}
 	}
 
-	if (wallpaperId !== undefined) {
-		if (wallpaperId === null) {
+	if (ps.wallpaperId !== undefined) {
+		if (ps.wallpaperId === null) {
 			updates.wallpaperUrl = null;
 			updates.wallpaperColor = null;
 		} else {
 			const wallpaper = await DriveFile.findOne({
-				_id: wallpaperId
+				_id: ps.wallpaperId
 			});
 
 			if (wallpaper == null) return rej('wallpaper not found');
@@ -142,7 +180,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
 	publishUserStream(user._id, 'meUpdated', iObj);
 
 	// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
-	if (user.isLocked && isLocked === false) {
+	if (user.isLocked && ps.isLocked === false) {
 		acceptAllFollowRequests(user);
 	}
 
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index b0876eaafd..18b0882f76 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -4,6 +4,7 @@
 import * as os from 'os';
 import config from '../../../config';
 import Meta from '../../../models/meta';
+import { ILocalUser } from '../../../models/user';
 
 const pkg = require('../../../../package.json');
 const client = require('../../../../built/client/meta.json');
@@ -11,7 +12,7 @@ const client = require('../../../../built/client/meta.json');
 /**
  * Show core info
  */
-export default () => new Promise(async (res, rej) => {
+export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
 	const meta: any = (await Meta.findOne()) || {};
 
 	res({
@@ -33,8 +34,10 @@ export default () => new Promise(async (res, rej) => {
 		},
 		broadcasts: meta.broadcasts,
 		disableRegistration: meta.disableRegistration,
+		disableLocalTimeline: meta.disableLocalTimeline,
 		driveCapacityPerLocalUserMb: config.localDriveCapacityMb,
 		recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
-		swPublickey: config.sw ? config.sw.public_key : null
+		swPublickey: config.sw ? config.sw.public_key : null,
+		hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined
 	});
 });
diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts
index 029bc1a95e..5fa58d19de 100644
--- a/src/server/api/endpoints/notes.ts
+++ b/src/server/api/endpoints/notes.ts
@@ -1,51 +1,65 @@
-/**
- * Module dependencies
- */
 import $ from 'cafy'; import ID from '../../../misc/cafy-id';
 import Note, { pack } from '../../../models/note';
+import getParams from '../get-params';
+
+export const meta = {
+	desc: {
+		'ja-JP': '投稿を取得します。'
+	},
+
+	params: {
+		local: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ローカルの投稿に限定するか否か'
+			}
+		}),
+
+		reply: $.bool.optional.note({
+			desc: {
+				'ja-JP': '返信に限定するか否か'
+			}
+		}),
+
+		renote: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'Renoteに限定するか否か'
+			}
+		}),
+
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		media: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		poll: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'アンケートが添付された投稿に限定するか否か'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10
+		}),
+
+		sinceId: $.type(ID).optional.note({}),
+
+		untilId: $.type(ID).optional.note({}),
+	}
+};
 
-/**
- * Get all notes
- */
 export default (params: any) => new Promise(async (res, rej) => {
-	// Get 'local' parameter
-	const [local, localErr] = $.bool.optional.get(params.local);
-	if (localErr) return rej('invalid local param');
-
-	// Get 'reply' parameter
-	const [reply, replyErr] = $.bool.optional.get(params.reply);
-	if (replyErr) return rej('invalid reply param');
-
-	// Get 'renote' parameter
-	const [renote, renoteErr] = $.bool.optional.get(params.renote);
-	if (renoteErr) return rej('invalid renote param');
-
-	// Get 'media' parameter
-	const [media, mediaErr] = $.bool.optional.get(params.media);
-	if (mediaErr) return rej('invalid media param');
-
-	// Get 'poll' parameter
-	const [poll, pollErr] = $.bool.optional.get(params.poll);
-	if (pollErr) return rej('invalid poll param');
-
-	// Get 'bot' parameter
-	//const [bot, botErr] = $.bool.optional.get(params.bot);
-	//if (botErr) return rej('invalid bot param');
-
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) return rej('invalid limit param');
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) return rej('invalid sinceId param');
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) return rej('invalid untilId param');
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if both of sinceId and untilId is specified
-	if (sinceId && untilId) {
+	if (ps.sinceId && ps.untilId) {
 		return rej('cannot set sinceId and untilId');
 	}
 
@@ -56,35 +70,37 @@ export default (params: any) => new Promise(async (res, rej) => {
 	const query = {
 		visibility: 'public'
 	} as any;
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
 	}
 
-	if (local) {
+	if (ps.local) {
 		query['_user.host'] = null;
 	}
 
-	if (reply != undefined) {
-		query.replyId = reply ? { $exists: true, $ne: null } : null;
+	if (ps.reply != undefined) {
+		query.replyId = ps.reply ? { $exists: true, $ne: null } : null;
 	}
 
-	if (renote != undefined) {
-		query.renoteId = renote ? { $exists: true, $ne: null } : null;
+	if (ps.renote != undefined) {
+		query.renoteId = ps.renote ? { $exists: true, $ne: null } : null;
 	}
 
-	if (media != undefined) {
-		query.mediaIds = media ? { $exists: true, $ne: null } : [];
+	const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media;
+
+	if (withFiles) {
+		query.fileIds = withFiles ? { $exists: true, $ne: null } : [];
 	}
 
-	if (poll != undefined) {
-		query.poll = poll ? { $exists: true, $ne: null } : null;
+	if (ps.poll != undefined) {
+		query.poll = ps.poll ? { $exists: true, $ne: null } : null;
 	}
 
 	// TODO
@@ -95,7 +111,7 @@ export default (params: any) => new Promise(async (res, rej) => {
 	// Issue query
 	const notes = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 04f5f7562e..96745132a3 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -71,9 +71,15 @@ export const meta = {
 			ref: 'geo'
 		}),
 
+		fileIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({
+			desc: {
+				'ja-JP': '添付するファイル'
+			}
+		}),
+
 		mediaIds: $.arr($.type(ID)).optional.unique().range(1, 4).note({
 			desc: {
-				'ja-JP': '添付するメディア'
+				'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)'
 			}
 		}),
 
@@ -124,26 +130,16 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
 	}
 
 	let files: IDriveFile[] = [];
-	if (ps.mediaIds !== undefined) {
-		// Fetch files
-		// forEach だと途中でエラーなどがあっても return できないので
-		// 敢えて for を使っています。
-		for (const mediaId of ps.mediaIds) {
-			// Fetch file
-			// SELECT _id
-			const entity = await DriveFile.findOne({
-				_id: mediaId,
+	const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null;
+	if (fileIds != null) {
+		files = await Promise.all(fileIds.map(fileId => {
+			return DriveFile.findOne({
+				_id: fileId,
 				'metadata.userId': user._id
 			});
+		}));
 
-			if (entity === null) {
-				return rej('file not found');
-			} else {
-				files.push(entity);
-			}
-		}
-	} else {
-		files = null;
+		files = files.filter(file => file != null);
 	}
 
 	let renote: INote = null;
@@ -155,7 +151,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
 
 		if (renote == null) {
 			return rej('renoteee is not found');
-		} else if (renote.renoteId && !renote.text && !renote.mediaIds) {
+		} else if (renote.renoteId && !renote.text && !renote.fileIds) {
 			return rej('cannot renote to renote');
 		}
 	}
@@ -176,7 +172,7 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
 		}
 
 		// 返信対象が引用でないRenoteだったらエラー
-		if (reply.renoteId && !reply.text && !reply.mediaIds) {
+		if (reply.renoteId && !reply.text && !reply.fileIds) {
 			return rej('cannot reply to renote');
 		}
 	}
@@ -191,13 +187,13 @@ export default (params: any, user: ILocalUser, app: IApp) => new Promise(async (
 
 	// テキストが無いかつ添付ファイルが無いかつRenoteも無いかつ投票も無かったらエラー
 	if ((ps.text === undefined || ps.text === null) && files === null && renote === null && ps.poll === undefined) {
-		return rej('text, mediaIds, renoteId or poll is required');
+		return rej('text, fileIds, renoteId or poll is required');
 	}
 
 	// 投稿を作成
 	const note = await create(user, {
 		createdAt: new Date(),
-		media: files,
+		files: files,
 		poll: ps.poll,
 		text: ps.text,
 		reply,
diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts
index 8f7233e308..5d93cd78ec 100644
--- a/src/server/api/endpoints/notes/global-timeline.ts
+++ b/src/server/api/endpoints/notes/global-timeline.ts
@@ -3,40 +3,50 @@ import Note from '../../../../models/note';
 import Mute from '../../../../models/mute';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+import { countIf } from '../../../../prelude/array';
+
+export const meta = {
+	desc: {
+		'ja-JP': 'グローバルタイムラインを取得します。'
+	},
+
+	params: {
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10
+		}),
+
+		sinceId: $.type(ID).optional.note({}),
+
+		untilId: $.type(ID).optional.note({}),
+
+		sinceDate: $.num.optional.note({}),
+
+		untilDate: $.num.optional.note({}),
+	}
+};
 
-/**
- * Get timeline of global
- */
 export default async (params: any, user: ILocalUser) => {
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) throw 'invalid limit param';
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) throw 'invalid sinceId param';
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) throw 'invalid untilId param';
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	// Get 'mediaOnly' parameter
-	const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
-	if (mediaOnlyErr) throw 'invalid mediaOnly param';
-
 	// ミュートしているユーザーを取得
 	const mutedUserIds = user ? (await Mute.find({
 		muterId: user._id
@@ -68,27 +78,29 @@ export default async (params: any, user: ILocalUser) => {
 		};
 	}
 
-	if (mediaOnly) {
-		query.mediaIds = { $exists: true, $ne: [] };
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
+		query.fileIds = { $exists: true, $ne: [] };
 	}
 
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 	//#endregion
@@ -96,7 +108,7 @@ export default async (params: any, user: ILocalUser) => {
 	// Issue query
 	const timeline = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts
index 2dbb1190c1..5e39d8c78a 100644
--- a/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -5,10 +5,9 @@ import { getFriends } from '../../common/get-friends';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
 import getParams from '../../get-params';
+import { countIf } from '../../../../prelude/array';
 
 export const meta = {
-	name: 'notes/hybrid-timeline',
-
 	desc: {
 		'ja-JP': 'ハイブリッドタイムラインを取得します。'
 	},
@@ -66,23 +65,26 @@ export const meta = {
 			}
 		}),
 
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
 		mediaOnly: $.bool.optional.note({
 			desc: {
-				'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		}),
 	}
 };
 
-/**
- * Get hybrid timeline of myself
- */
 export default async (params: any, user: ILocalUser) => {
 	const [ps, psErr] = getParams(meta, params);
 	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
+	if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
@@ -164,7 +166,7 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
@@ -180,7 +182,7 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
@@ -196,16 +198,16 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
 		});
 	}
 
-	if (ps.mediaOnly) {
+	if (ps.withFiles || ps.mediaOnly) {
 		query.$and.push({
-			mediaIds: { $exists: true, $ne: [] }
+			fileIds: { $exists: true, $ne: [] }
 		});
 	}
 
diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts
index bbcc6303ca..ff10e6fbaa 100644
--- a/src/server/api/endpoints/notes/local-timeline.ts
+++ b/src/server/api/endpoints/notes/local-timeline.ts
@@ -3,40 +3,56 @@ import Note from '../../../../models/note';
 import Mute from '../../../../models/mute';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+import { countIf } from '../../../../prelude/array';
+
+export const meta = {
+	desc: {
+		'ja-JP': 'ローカルタイムラインを取得します。'
+	},
+
+	params: {
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		fileType: $.arr($.str).optional.note({
+			desc: {
+				'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10
+		}),
+
+		sinceId: $.type(ID).optional.note({}),
+
+		untilId: $.type(ID).optional.note({}),
+
+		sinceDate: $.num.optional.note({}),
+
+		untilDate: $.num.optional.note({}),
+	}
+};
 
-/**
- * Get timeline of local
- */
 export default async (params: any, user: ILocalUser) => {
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) throw 'invalid limit param';
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) throw 'invalid sinceId param';
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) throw 'invalid untilId param';
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	// Get 'mediaOnly' parameter
-	const [mediaOnly, mediaOnlyErr] = $.bool.optional.get(params.mediaOnly);
-	if (mediaOnlyErr) throw 'invalid mediaOnly param';
-
 	// ミュートしているユーザーを取得
 	const mutedUserIds = user ? (await Mute.find({
 		muterId: user._id
@@ -69,27 +85,37 @@ export default async (params: any, user: ILocalUser) => {
 		};
 	}
 
-	if (mediaOnly) {
-		query.mediaIds = { $exists: true, $ne: [] };
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
+		query.fileIds = { $exists: true, $ne: [] };
 	}
 
-	if (sinceId) {
+	if (ps.fileType) {
+		query.fileIds = { $exists: true, $ne: [] };
+
+		query['_files.contentType'] = {
+			$in: ps.fileType
+		};
+	}
+
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 	//#endregion
@@ -97,7 +123,7 @@ export default async (params: any, user: ILocalUser) => {
 	// Issue query
 	const timeline = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts
index 0781db16c5..ec68f065d8 100644
--- a/src/server/api/endpoints/notes/reactions/create.ts
+++ b/src/server/api/endpoints/notes/reactions/create.ts
@@ -43,6 +43,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
 		return rej('note not found');
 	}
 
+	if (note.deletedAt != null) {
+		return rej('this not is already deleted');
+	}
+
 	try {
 		await create(user, note, ps.reaction);
 	} catch (e) {
diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts
index e092275fe8..11bfe34724 100644
--- a/src/server/api/endpoints/notes/search_by_tag.ts
+++ b/src/server/api/endpoints/notes/search_by_tag.ts
@@ -4,119 +4,154 @@ import User, { ILocalUser } from '../../../../models/user';
 import Mute from '../../../../models/mute';
 import { getFriendIds } from '../../common/get-friends';
 import { pack } from '../../../../models/note';
+import getParams from '../../get-params';
+import { erase } from '../../../../prelude/array';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定されたタグが付けられた投稿を取得します。'
+	},
+
+	params: {
+		tag: $.str.note({
+			desc: {
+				'ja-JP': 'タグ'
+			}
+		}),
+
+		includeUserIds: $.arr($.type(ID)).optional.note({
+			default: []
+		}),
+
+		excludeUserIds: $.arr($.type(ID)).optional.note({
+			default: []
+		}),
+
+		includeUserUsernames: $.arr($.str).optional.note({
+			default: []
+		}),
+
+		excludeUserUsernames: $.arr($.str).optional.note({
+			default: []
+		}),
+
+		following: $.bool.optional.nullable.note({
+			default: null
+		}),
+
+		mute: $.str.optional.note({
+			default: 'mute_all'
+		}),
+
+		reply: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': '返信に限定するか否か'
+			}
+		}),
+
+		renote: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'Renoteに限定するか否か'
+			}
+		}),
+
+		withFiles: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か'
+			}
+		}),
+
+		media: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+
+		poll: $.bool.optional.nullable.note({
+			default: null,
+
+			desc: {
+				'ja-JP': 'アンケートが添付された投稿に限定するか否か'
+			}
+		}),
+
+		sinceDate: $.num.optional.note({
+		}),
+
+		untilDate: $.num.optional.note({
+		}),
+
+		offset: $.num.optional.min(0).note({
+			default: 0
+		}),
+
+		limit: $.num.optional.range(1, 30).note({
+			default: 10
+		}),
+	}
+};
 
-/**
- * Search notes by tag
- */
 export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
-	// Get 'tag' parameter
-	const [tag, tagError] = $.str.get(params.tag);
-	if (tagError) return rej('invalid tag param');
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
-	// Get 'includeUserIds' parameter
-	const [includeUserIds = [], includeUserIdsErr] = $.arr($.type(ID)).optional.get(params.includeUserIds);
-	if (includeUserIdsErr) return rej('invalid includeUserIds param');
-
-	// Get 'excludeUserIds' parameter
-	const [excludeUserIds = [], excludeUserIdsErr] = $.arr($.type(ID)).optional.get(params.excludeUserIds);
-	if (excludeUserIdsErr) return rej('invalid excludeUserIds param');
-
-	// Get 'includeUserUsernames' parameter
-	const [includeUserUsernames = [], includeUserUsernamesErr] = $.arr($.str).optional.get(params.includeUserUsernames);
-	if (includeUserUsernamesErr) return rej('invalid includeUserUsernames param');
-
-	// Get 'excludeUserUsernames' parameter
-	const [excludeUserUsernames = [], excludeUserUsernamesErr] = $.arr($.str).optional.get(params.excludeUserUsernames);
-	if (excludeUserUsernamesErr) return rej('invalid excludeUserUsernames param');
-
-	// Get 'following' parameter
-	const [following = null, followingErr] = $.bool.optional.nullable.get(params.following);
-	if (followingErr) return rej('invalid following param');
-
-	// Get 'mute' parameter
-	const [mute = 'mute_all', muteErr] = $.str.optional.get(params.mute);
-	if (muteErr) return rej('invalid mute param');
-
-	// Get 'reply' parameter
-	const [reply = null, replyErr] = $.bool.optional.nullable.get(params.reply);
-	if (replyErr) return rej('invalid reply param');
-
-	// Get 'renote' parameter
-	const [renote = null, renoteErr] = $.bool.optional.nullable.get(params.renote);
-	if (renoteErr) return rej('invalid renote param');
-
-	// Get 'media' parameter
-	const [media = null, mediaErr] = $.bool.optional.nullable.get(params.media);
-	if (mediaErr) return rej('invalid media param');
-
-	// Get 'poll' parameter
-	const [poll = null, pollErr] = $.bool.optional.nullable.get(params.poll);
-	if (pollErr) return rej('invalid poll param');
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
-
-	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset);
-	if (offsetErr) return rej('invalid offset param');
-
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 30).get(params.limit);
-	if (limitErr) return rej('invalid limit param');
-
-	if (includeUserUsernames != null) {
-		const ids = (await Promise.all(includeUserUsernames.map(async (username) => {
+	if (ps.includeUserUsernames != null) {
+		const ids = erase(null, await Promise.all(ps.includeUserUsernames.map(async (username) => {
 			const _user = await User.findOne({
 				usernameLower: username.toLowerCase()
 			});
 			return _user ? _user._id : null;
-		}))).filter(id => id != null);
+		})));
 
-		ids.forEach(id => includeUserIds.push(id));
+		ids.forEach(id => ps.includeUserIds.push(id));
 	}
 
-	if (excludeUserUsernames != null) {
-		const ids = (await Promise.all(excludeUserUsernames.map(async (username) => {
+	if (ps.excludeUserUsernames != null) {
+		const ids = erase(null, await Promise.all(ps.excludeUserUsernames.map(async (username) => {
 			const _user = await User.findOne({
 				usernameLower: username.toLowerCase()
 			});
 			return _user ? _user._id : null;
-		}))).filter(id => id != null);
+		})));
 
-		ids.forEach(id => excludeUserIds.push(id));
+		ids.forEach(id => ps.excludeUserIds.push(id));
 	}
 
-	let q: any = {
+	const q: any = {
 		$and: [{
-			tagsLower: tag.toLowerCase()
-		}]
+			tagsLower: ps.tag.toLowerCase()
+		}],
+		deletedAt: { $exists: false }
 	};
 
 	const push = (x: any) => q.$and.push(x);
 
-	if (includeUserIds && includeUserIds.length != 0) {
+	if (ps.includeUserIds && ps.includeUserIds.length != 0) {
 		push({
 			userId: {
-				$in: includeUserIds
+				$in: ps.includeUserIds
 			}
 		});
-	} else if (excludeUserIds && excludeUserIds.length != 0) {
+	} else if (ps.excludeUserIds && ps.excludeUserIds.length != 0) {
 		push({
 			userId: {
-				$nin: excludeUserIds
+				$nin: ps.excludeUserIds
 			}
 		});
 	}
 
-	if (following != null && me != null) {
+	if (ps.following != null && me != null) {
 		const ids = await getFriendIds(me._id, false);
 		push({
-			userId: following ? {
+			userId: ps.following ? {
 				$in: ids
 			} : {
 				$nin: ids.concat(me._id)
@@ -131,7 +166,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		});
 		const mutedUserIds = mutes.map(m => m.muteeId);
 
-		switch (mute) {
+		switch (ps.mute) {
 			case 'mute_all':
 				push({
 					userId: {
@@ -202,8 +237,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (reply != null) {
-		if (reply) {
+	if (ps.reply != null) {
+		if (ps.reply) {
 			push({
 				replyId: {
 					$exists: true,
@@ -223,8 +258,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (renote != null) {
-		if (renote) {
+	if (ps.renote != null) {
+		if (ps.renote) {
 			push({
 				renoteId: {
 					$exists: true,
@@ -244,10 +279,12 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (media != null) {
-		if (media) {
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.media;
+
+	if (withFiles != null) {
+		if (withFiles) {
 			push({
-				mediaIds: {
+				fileIds: {
 					$exists: true,
 					$ne: null
 				}
@@ -255,18 +292,18 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		} else {
 			push({
 				$or: [{
-					mediaIds: {
+					fileIds: {
 						$exists: false
 					}
 				}, {
-					mediaIds: null
+					fileIds: null
 				}]
 			});
 		}
 	}
 
-	if (poll != null) {
-		if (poll) {
+	if (ps.poll != null) {
+		if (ps.poll) {
 			push({
 				poll: {
 					$exists: true,
@@ -286,24 +323,24 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		}
 	}
 
-	if (sinceDate) {
+	if (ps.sinceDate) {
 		push({
 			createdAt: {
-				$gt: new Date(sinceDate)
+				$gt: new Date(ps.sinceDate)
 			}
 		});
 	}
 
-	if (untilDate) {
+	if (ps.untilDate) {
 		push({
 			createdAt: {
-				$lt: new Date(untilDate)
+				$lt: new Date(ps.untilDate)
 			}
 		});
 	}
 
 	if (q.$and.length == 0) {
-		q = {};
+		delete q.$and;
 	}
 
 	// Search notes
@@ -312,8 +349,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 			sort: {
 				_id: -1
 			},
-			limit: limit,
-			skip: offset
+			limit: ps.limit,
+			skip: ps.offset
 		});
 
 	// Serialize
diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts
index 099bf2010b..5f3844987c 100644
--- a/src/server/api/endpoints/notes/timeline.ts
+++ b/src/server/api/endpoints/notes/timeline.ts
@@ -5,6 +5,7 @@ import { getFriends } from '../../common/get-friends';
 import { pack } from '../../../../models/note';
 import { ILocalUser } from '../../../../models/user';
 import getParams from '../../get-params';
+import { countIf } from '../../../../prelude/array';
 
 export const meta = {
 	desc: {
@@ -67,9 +68,15 @@ export const meta = {
 			}
 		}),
 
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
 		mediaOnly: $.bool.optional.note({
 			desc: {
-				'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		}),
 	}
@@ -80,7 +87,7 @@ export default async (params: any, user: ILocalUser) => {
 	if (psErr) throw psErr;
 
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate].filter(x => x != null).length > 1) {
+	if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
@@ -154,7 +161,7 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
@@ -170,7 +177,7 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
@@ -186,16 +193,18 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
 		});
 	}
 
-	if (ps.mediaOnly) {
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
 		query.$and.push({
-			mediaIds: { $exists: true, $ne: [] }
+			fileIds: { $exists: true, $ne: [] }
 		});
 	}
 
diff --git a/src/server/api/endpoints/notes/trend.ts b/src/server/api/endpoints/notes/trend.ts
index 7a0a098f28..9f55ed3243 100644
--- a/src/server/api/endpoints/notes/trend.ts
+++ b/src/server/api/endpoints/notes/trend.ts
@@ -52,7 +52,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
 	}
 
 	if (media != undefined) {
-		query.mediaIds = media ? { $exists: true, $ne: null } : null;
+		query.fileIds = media ? { $exists: true, $ne: null } : null;
 	}
 
 	if (poll != undefined) {
diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts
index a7b43014ed..61192d7d3e 100644
--- a/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -73,9 +73,15 @@ export const meta = {
 			}
 		}),
 
+		withFiles: $.bool.optional.note({
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
 		mediaOnly: $.bool.optional.note({
 			desc: {
-				'ja-JP': 'true にすると、メディアが添付された投稿だけ取得します'
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
 			}
 		}),
 	}
@@ -160,7 +166,7 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
@@ -176,7 +182,7 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
@@ -192,16 +198,18 @@ export default async (params: any, user: ILocalUser) => {
 			}, {
 				text: { $ne: null }
 			}, {
-				mediaIds: { $ne: [] }
+				fileIds: { $ne: [] }
 			}, {
 				poll: { $ne: null }
 			}]
 		});
 	}
 
-	if (ps.mediaOnly) {
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
 		query.$and.push({
-			mediaIds: { $exists: true, $ne: [] }
+			fileIds: { $exists: true, $ne: [] }
 		});
 	}
 
diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts
index 9411873573..7fe3ca9943 100644
--- a/src/server/api/endpoints/users/followers.ts
+++ b/src/server/api/endpoints/users/followers.ts
@@ -73,8 +73,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 	}
 
 	// Serialize
-	const users = await Promise.all(following.map(async f =>
-		await pack(f.followerId, me, { detail: true })));
+	const users = await Promise.all(following.map(f => pack(f.followerId, me, { detail: true })));
 
 	// Response
 	res({
diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts
index 7a64d15d7b..0e564fd1b6 100644
--- a/src/server/api/endpoints/users/following.ts
+++ b/src/server/api/endpoints/users/following.ts
@@ -73,8 +73,7 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 	}
 
 	// Serialize
-	const users = await Promise.all(following.map(async f =>
-		await pack(f.followeeId, me, { detail: true })));
+	const users = await Promise.all(following.map(f => pack(f.followeeId, me, { detail: true })));
 
 	// Response
 	res({
diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts
index ff7855bde0..1ab7786a18 100644
--- a/src/server/api/endpoints/users/notes.ts
+++ b/src/server/api/endpoints/users/notes.ts
@@ -2,63 +2,122 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id';
 import getHostLower from '../../common/get-host-lower';
 import Note, { pack } from '../../../../models/note';
 import User, { ILocalUser } from '../../../../models/user';
+import getParams from '../../get-params';
+import { countIf } from '../../../../prelude/array';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定したユーザーのタイムラインを取得します。'
+	},
+
+	params: {
+		userId: $.type(ID).optional.note({
+			desc: {
+				'ja-JP': 'ユーザーID'
+			}
+		}),
+
+		username: $.str.optional.note({
+			desc: {
+				'ja-JP': 'ユーザー名'
+			}
+		}),
+
+		host: $.str.optional.note({
+		}),
+
+		includeReplies: $.bool.optional.note({
+			default: true,
+
+			desc: {
+				'ja-JP': 'リプライを含めるか否か'
+			}
+		}),
+
+		limit: $.num.optional.range(1, 100).note({
+			default: 10,
+			desc: {
+				'ja-JP': '最大数'
+			}
+		}),
+
+		sinceId: $.type(ID).optional.note({
+			desc: {
+				'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します'
+			}
+		}),
+
+		untilId: $.type(ID).optional.note({
+			desc: {
+				'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します'
+			}
+		}),
+
+		sinceDate: $.num.optional.note({
+			desc: {
+				'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
+			}
+		}),
+
+		untilDate: $.num.optional.note({
+			desc: {
+				'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
+			}
+		}),
+
+		includeMyRenotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				'ja-JP': '自分の行ったRenoteを含めるかどうか'
+			}
+		}),
+
+		includeRenotedMyNotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				'ja-JP': 'Renoteされた自分の投稿を含めるかどうか'
+			}
+		}),
+
+		includeLocalRenotes: $.bool.optional.note({
+			default: true,
+			desc: {
+				'ja-JP': 'Renoteされたローカルの投稿を含めるかどうか'
+			}
+		}),
+
+		withFiles: $.bool.optional.note({
+			default: false,
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します'
+			}
+		}),
+
+		mediaOnly: $.bool.optional.note({
+			default: false,
+			desc: {
+				'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)'
+			}
+		}),
+	}
+};
 
-/**
- * Get notes of a user
- */
 export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => {
-	// Get 'userId' parameter
-	const [userId, userIdErr] = $.type(ID).optional.get(params.userId);
-	if (userIdErr) return rej('invalid userId param');
+	const [ps, psErr] = getParams(meta, params);
+	if (psErr) throw psErr;
 
-	// Get 'username' parameter
-	const [username, usernameErr] = $.str.optional.get(params.username);
-	if (usernameErr) return rej('invalid username param');
-
-	if (userId === undefined && username === undefined) {
+	if (ps.userId === undefined && ps.username === undefined) {
 		return rej('userId or username is required');
 	}
 
-	// Get 'host' parameter
-	const [host, hostErr] = $.str.optional.get(params.host);
-	if (hostErr) return rej('invalid host param');
-
-	// Get 'includeReplies' parameter
-	const [includeReplies = true, includeRepliesErr] = $.bool.optional.get(params.includeReplies);
-	if (includeRepliesErr) return rej('invalid includeReplies param');
-
-	// Get 'withMedia' parameter
-	const [withMedia = false, withMediaErr] = $.bool.optional.get(params.withMedia);
-	if (withMediaErr) return rej('invalid withMedia param');
-
-	// Get 'limit' parameter
-	const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit);
-	if (limitErr) return rej('invalid limit param');
-
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId);
-	if (sinceIdErr) return rej('invalid sinceId param');
-
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId);
-	if (untilIdErr) return rej('invalid untilId param');
-
-	// Get 'sinceDate' parameter
-	const [sinceDate, sinceDateErr] = $.num.optional.get(params.sinceDate);
-	if (sinceDateErr) throw 'invalid sinceDate param';
-
-	// Get 'untilDate' parameter
-	const [untilDate, untilDateErr] = $.num.optional.get(params.untilDate);
-	if (untilDateErr) throw 'invalid untilDate param';
-
 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
-	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+	if (countIf(x => x != null, [ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate]) > 1) {
 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
 	}
 
-	const q = userId !== undefined
-		? { _id: userId }
-		: { usernameLower: username.toLowerCase(), host: getHostLower(host) } ;
+	const q = ps.userId !== undefined
+		? { _id: ps.userId }
+		: { usernameLower: ps.username.toLowerCase(), host: getHostLower(ps.host) } ;
 
 	// Lookup user
 	const user = await User.findOne(q, {
@@ -80,32 +139,34 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 		userId: user._id
 	} as any;
 
-	if (sinceId) {
+	if (ps.sinceId) {
 		sort._id = 1;
 		query._id = {
-			$gt: sinceId
+			$gt: ps.sinceId
 		};
-	} else if (untilId) {
+	} else if (ps.untilId) {
 		query._id = {
-			$lt: untilId
+			$lt: ps.untilId
 		};
-	} else if (sinceDate) {
+	} else if (ps.sinceDate) {
 		sort._id = 1;
 		query.createdAt = {
-			$gt: new Date(sinceDate)
+			$gt: new Date(ps.sinceDate)
 		};
-	} else if (untilDate) {
+	} else if (ps.untilDate) {
 		query.createdAt = {
-			$lt: new Date(untilDate)
+			$lt: new Date(ps.untilDate)
 		};
 	}
 
-	if (!includeReplies) {
+	if (!ps.includeReplies) {
 		query.replyId = null;
 	}
 
-	if (withMedia) {
-		query.mediaIds = {
+	const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly;
+
+	if (withFiles) {
+		query.fileIds = {
 			$exists: true,
 			$ne: []
 		};
@@ -115,12 +176,10 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) =>
 	// Issue query
 	const notes = await Note
 		.find(query, {
-			limit: limit,
+			limit: ps.limit,
 			sort: sort
 		});
 
 	// Serialize
-	res(await Promise.all(notes.map(async (note) =>
-		await pack(note, me)
-	)));
+	res(await Promise.all(notes.map(note => pack(note, me))));
 });
diff --git a/src/server/api/stream/home.ts b/src/server/api/stream/home.ts
index dc3ce9d19f..5f3b6744b2 100644
--- a/src/server/api/stream/home.ts
+++ b/src/server/api/stream/home.ts
@@ -36,6 +36,13 @@ export default async function(
 
 	// Subscribe Home stream channel
 	subscriber.on(`user-stream:${user._id}`, async x => {
+		// Renoteなら再pack
+		if (x.type == 'note' && x.body.renoteId != null) {
+			x.body.renote = await pack(x.body.renoteId, user, {
+				detail: true
+			});
+		}
+
 		//#region 流れてきたメッセージがミュートしているユーザーが関わるものだったら無視する
 		if (x.type == 'note') {
 			if (mutedUserIds.includes(x.body.userId)) {
@@ -54,13 +61,6 @@ export default async function(
 		}
 		//#endregion
 
-		// Renoteなら再pack
-		if (x.type == 'note' && x.body.renoteId != null) {
-			x.body.renote = await pack(x.body.renoteId, user, {
-				detail: true
-			});
-		}
-
 		connection.send(JSON.stringify(x));
 	});
 
diff --git a/src/server/api/stream/hybrid-timeline.ts b/src/server/api/stream/hybrid-timeline.ts
index c401145abe..d0dae9b0dd 100644
--- a/src/server/api/stream/hybrid-timeline.ts
+++ b/src/server/api/stream/hybrid-timeline.ts
@@ -19,6 +19,13 @@ export default async function(
 	subscriber.on(`hybrid-timeline:${user._id}`, onEvent);
 
 	async function onEvent(note: any) {
+		// Renoteなら再pack
+		if (note.renoteId != null) {
+			note.renote = await pack(note.renoteId, user, {
+				detail: true
+			});
+		}
+
 		//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 		if (mutedUserIds.indexOf(note.userId) != -1) {
 			return;
@@ -31,13 +38,6 @@ export default async function(
 		}
 		//#endregion
 
-		// Renoteなら再pack
-		if (note.renoteId != null) {
-			note.renote = await pack(note.renoteId, user, {
-				detail: true
-			});
-		}
-
 		connection.send(JSON.stringify({
 			type: 'note',
 			body: note
diff --git a/src/server/api/stream/local-timeline.ts b/src/server/api/stream/local-timeline.ts
index 25e0e00c9f..e21c071bab 100644
--- a/src/server/api/stream/local-timeline.ts
+++ b/src/server/api/stream/local-timeline.ts
@@ -16,6 +16,13 @@ export default async function(
 
 	// Subscribe stream
 	subscriber.on('local-timeline', async note => {
+		// Renoteなら再pack
+		if (note.renoteId != null) {
+			note.renote = await pack(note.renoteId, user, {
+				detail: true
+			});
+		}
+
 		//#region 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 		if (mutedUserIds.indexOf(note.userId) != -1) {
 			return;
@@ -28,13 +35,6 @@ export default async function(
 		}
 		//#endregion
 
-		// Renoteなら再pack
-		if (note.renoteId != null) {
-			note.renote = await pack(note.renoteId, user, {
-				detail: true
-			});
-		}
-
 		connection.send(JSON.stringify({
 			type: 'note',
 			body: note
diff --git a/src/server/index.ts b/src/server/index.ts
index f1fcf58c8d..dc60b0d9ec 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -11,11 +11,13 @@ import * as Router from 'koa-router';
 import * as mount from 'koa-mount';
 import * as compress from 'koa-compress';
 import * as logger from 'koa-logger';
+const requestStats = require('request-stats');
 //const slow = require('koa-slow');
 
 import activityPub from './activitypub';
 import webFinger from './webfinger';
 import config from '../config';
+import { updateNetworkStats } from '../services/update-chart';
 
 // Init app
 const app = new Koa();
@@ -81,4 +83,27 @@ export default () => new Promise(resolve => {
 
 	// Listen
 	server.listen(config.port, resolve);
+
+	//#region Network stats
+	let queue: any[] = [];
+
+	requestStats(server, (stats: any) => {
+		if (stats.ok) {
+			queue.push(stats);
+		}
+	});
+
+	// Bulk write
+	setInterval(() => {
+		if (queue.length == 0) return;
+
+		const requests = queue.length;
+		const time = queue.reduce((a, b) => a + b.time, 0);
+		const incomingBytes = queue.reduce((a, b) => a + b.req.bytes, 0);
+		const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0);
+		queue = [];
+
+		updateNetworkStats(requests, time, incomingBytes, outgoingBytes);
+	}, 5000);
+	//#endregion
 });
diff --git a/src/server/web/views/note.pug b/src/server/web/views/note.pug
index 22f1834059..234ecabe22 100644
--- a/src/server/web/views/note.pug
+++ b/src/server/web/views/note.pug
@@ -6,7 +6,7 @@ block vars
 	- const url = `${config.url}/notes/${note.id}`;
 
 block title
-	= `${title} | Misskey`
+	= `${title} | ${config.name}`
 
 block desc
 	meta(name='description' content= summary)
@@ -23,3 +23,6 @@ block meta
 		link(rel='prev' href=`${config.url}/notes/${note.prev}`)
 	if note.next
 		link(rel='next' href=`${config.url}/notes/${note.next}`)
+
+	if !user.host
+		link(rel='alternate' href=url type='application/activity+json')
diff --git a/src/server/web/views/user.pug b/src/server/web/views/user.pug
index 98a53ab549..506a889d98 100644
--- a/src/server/web/views/user.pug
+++ b/src/server/web/views/user.pug
@@ -6,7 +6,7 @@ block vars
 	- const img = user.avatarId ? `${config.drive_url}/${user.avatarId}` : null;
 
 block title
-	= `${title} | Misskey`
+	= `${title} | ${config.name}`
 
 block desc
 	meta(name='description' content= user.description)
@@ -18,3 +18,10 @@ block meta
 	meta(property='og:description' content= user.description)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= img)
+	
+	if !user.host
+		link(rel='alternate' href=`${config.url}/users/${user._id}` type='application/activity+json')
+	if user.uri
+		link(rel='alternate' href=user.uri type='application/activity+json')
+	if user.url
+		link(rel='alternate' href=user.url type='text/html')
diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts
index d1c7051ab0..666a6ca742 100644
--- a/src/services/drive/add-file.ts
+++ b/src/services/drive/add-file.ts
@@ -36,8 +36,11 @@ async function save(path: string, name: string, type: string, hash: string, size
 
 	if (config.drive && config.drive.storage == 'minio') {
 		const minio = new Minio.Client(config.drive.config);
-		const key = `${config.drive.prefix}/${uuid.v4()}/${name}`;
-		const thumbnailKey = `${config.drive.prefix}/${uuid.v4()}/${name}.thumbnail.jpg`;
+
+		const keyDir = `${config.drive.prefix}/${uuid.v4()}`;
+		const key = `${keyDir}/${name}`;
+		const thumbnailKeyDir = `${config.drive.prefix}/${uuid.v4()}`;
+		const thumbnailKey = `${thumbnailKeyDir}/${name}.thumbnail.jpg`;
 
 		const baseUrl = config.drive.baseUrl
 			|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }`;
@@ -61,8 +64,8 @@ async function save(path: string, name: string, type: string, hash: string, size
 				key: key,
 				thumbnailKey: thumbnailKey
 			},
-			url: `${ baseUrl }/${ key }`,
-			thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailKey }` : null
+			url: `${ baseUrl }/${ keyDir }/${ encodeURIComponent(name) }`,
+			thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailKeyDir }/${ encodeURIComponent(name) }.thumbnail.jpg` : null
 		});
 
 		const file = await DriveFile.insert({
@@ -150,7 +153,7 @@ export default async function(
 	isLink: boolean = false,
 	url: string = null,
 	uri: string = null,
-	sensitive = false
+	sensitive: boolean = null
 ): Promise<IDriveFile> {
 	// Calc md5 hash
 	const calcHash = new Promise<string>((res, rej) => {
@@ -326,7 +329,13 @@ export default async function(
 		properties: properties,
 		withoutChunks: isLink,
 		isRemote: isLink,
-		isSensitive: sensitive
+		isSensitive: (sensitive !== null && sensitive !== undefined)
+			? sensitive
+			: isLocalUser(user)
+				? user.settings.alwaysMarkNsfw
+					? true
+					: false
+				: false
 	} as IMetadata;
 
 	if (url !== null) {
diff --git a/src/services/drive/upload-from-url.ts b/src/services/drive/upload-from-url.ts
index 0cf21ea5a2..35d4ec9883 100644
--- a/src/services/drive/upload-from-url.ts
+++ b/src/services/drive/upload-from-url.ts
@@ -34,8 +34,9 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul
 	// write content at URL to temp file
 	await new Promise((res, rej) => {
 		const writable = fs.createWriteStream(path);
+		const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
 		request({
-			url,
+			url: requestUrl,
 			headers: {
 				'User-Agent': config.user_agent
 			}
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index 63e3557828..771e9cade8 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -24,6 +24,7 @@ import isQuote from '../../misc/is-quote';
 import { TextElementMention } from '../../mfm/parse/elements/mention';
 import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
 import { updateNoteStats } from '../update-chart';
+import { erase, unique } from '../../prelude/array';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -84,7 +85,7 @@ type Option = {
 	text?: string;
 	reply?: INote;
 	renote?: INote;
-	media?: IDriveFile[];
+	files?: IDriveFile[];
 	geo?: any;
 	poll?: any;
 	viaMobile?: boolean;
@@ -103,23 +104,25 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
 	if (data.viaMobile == null) data.viaMobile = false;
 
 	if (data.visibleUsers) {
-		data.visibleUsers = data.visibleUsers.filter(x => x != null);
+		data.visibleUsers = erase(null, data.visibleUsers);
 	}
 
+	// リプライ対象が削除された投稿だったらreject
 	if (data.reply && data.reply.deletedAt != null) {
 		return rej();
 	}
 
+	// Renote対象が削除された投稿だったらreject
 	if (data.renote && data.renote.deletedAt != null) {
 		return rej();
 	}
 
-	// リプライ先が自分以外の非公開の投稿なら禁止
+	// リプライ対象が自分以外の非公開の投稿なら禁止
 	if (data.reply && data.reply.visibility == 'private' && !data.reply.userId.equals(user._id)) {
 		return rej();
 	}
 
-	// Renote先が自分以外の非公開の投稿なら禁止
+	// Renote対象が自分以外の非公開の投稿なら禁止
 	if (data.renote && data.renote.visibility == 'private' && !data.renote.userId.equals(user._id)) {
 		return rej();
 	}
@@ -135,7 +138,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
 
 	const mentionedUsers = await extractMentionedUsers(tokens);
 
-	const note = await insertNote(user, data, tokens, tags, mentionedUsers);
+	const note = await insertNote(user, data, tags, mentionedUsers);
 
 	res(note);
 
@@ -181,7 +184,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
 
 	const noteActivity = await renderActivity(data, note);
 
-	if (isLocalUser(user)) {
+	if (isLocalUser(user) && note.visibility != 'private') {
 		deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
 	}
 
@@ -238,7 +241,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
 });
 
 async function renderActivity(data: Option, note: INote) {
-	const content = data.renote && data.text == null
+	const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length == 0)
 		? renderAnnounce(data.renote.uri ? data.renote.uri : `${config.url}/notes/${data.renote._id}`, note)
 		: renderCreate(await renderNote(note, false), note);
 
@@ -266,10 +269,12 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
 		}
 
 		if (['private', 'followers', 'specified'].includes(note.visibility)) {
-			// Publish event to myself's stream
-			publishUserStream(note.userId, 'note', await pack(note, user, {
+			const detailPackedNote = await pack(note, user, {
 				detail: true
-			}));
+			});
+			// Publish event to myself's stream
+			publishUserStream(note.userId, 'note', detailPackedNote);
+			publishHybridTimelineStream(note.userId, detailPackedNote);
 		} else {
 			// Publish event to myself's stream
 			publishUserStream(note.userId, 'note', noteObj);
@@ -281,6 +286,9 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
 
 			if (note.visibility == 'public') {
 				publishHybridTimelineStream(null, noteObj);
+			} else {
+				// Publish event to myself's stream
+				publishHybridTimelineStream(note.userId, noteObj);
 			}
 		}
 	}
@@ -309,10 +317,10 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
 	publishToUserLists(note, noteObj);
 }
 
-async function insertNote(user: IUser, data: Option, tokens: ReturnType<typeof parse>, tags: string[], mentionedUsers: IUser[]) {
+async function insertNote(user: IUser, data: Option, tags: string[], mentionedUsers: IUser[]) {
 	const insert: any = {
 		createdAt: data.createdAt,
-		mediaIds: data.media ? data.media.map(file => file._id) : [],
+		fileIds: data.files ? data.files.map(file => file._id) : [],
 		replyId: data.reply ? data.reply._id : null,
 		renoteId: data.renote ? data.renote._id : null,
 		text: data.text,
@@ -347,7 +355,8 @@ async function insertNote(user: IUser, data: Option, tokens: ReturnType<typeof p
 		_user: {
 			host: user.host,
 			inbox: isRemoteUser(user) ? user.inbox : undefined
-		}
+		},
+		_files: data.files ? data.files : []
 	};
 
 	if (data.uri != null) insert.uri = data.uri;
@@ -383,7 +392,7 @@ function extractHashtags(tokens: ReturnType<typeof parse>): string[] {
 		.map(t => (t as TextElementHashtag).hashtag)
 		.filter(tag => tag.length <= 100);
 
-	return [...new Set(hashtags)];
+	return unique(hashtags);
 }
 
 function index(note: INote) {
@@ -440,6 +449,11 @@ async function publishToUserLists(note: INote, noteObj: any) {
 }
 
 async function publishToFollowers(note: INote, noteObj: any, user: IUser, noteActivity: any) {
+	const detailPackedNote = await pack(note, null, {
+		detail: true,
+		skipHide: true
+	});
+
 	const followers = await Following.find({
 		followeeId: note.userId
 	});
@@ -458,10 +472,10 @@ async function publishToFollowers(note: INote, noteObj: any, user: IUser, noteAc
 			}
 
 			// Publish event to followers stream
-			publishUserStream(following.followerId, 'note', noteObj);
+			publishUserStream(following.followerId, 'note', detailPackedNote);
 
 			if (isRemoteUser(user) || note.visibility != 'public') {
-				publishHybridTimelineStream(following.followerId, noteObj);
+				publishHybridTimelineStream(following.followerId, detailPackedNote);
 			}
 		} else {
 			// フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信
@@ -540,20 +554,20 @@ function incNotesCount(user: IUser) {
 async function extractMentionedUsers(tokens: ReturnType<typeof parse>): Promise<IUser[]> {
 	if (tokens == null) return [];
 
-	const mentionTokens = [...new Set(
+	const mentionTokens = unique(
 		tokens
 			.filter(t => t.type == 'mention') as TextElementMention[]
-	)];
+	);
 
-	const mentionedUsers = [...new Set(
-		(await Promise.all(mentionTokens.map(async m => {
+	const mentionedUsers = unique(
+		erase(null, await Promise.all(mentionTokens.map(async m => {
 			try {
 				return await resolveUser(m.username, m.host);
 			} catch (e) {
 				return null;
 			}
-		}))).filter(x => x != null)
-	)];
+		})))
+	);
 
 	return mentionedUsers;
 }
diff --git a/src/services/note/delete.ts b/src/services/note/delete.ts
index dea306feec..b164d59781 100644
--- a/src/services/note/delete.ts
+++ b/src/services/note/delete.ts
@@ -23,9 +23,10 @@ export default async function(user: IUser, note: INote) {
 			deletedAt: new Date(),
 			text: null,
 			tags: [],
-			mediaIds: [],
+			fileIds: [],
 			poll: null,
-			geo: null
+			geo: null,
+			cw: null
 		}
 	});
 
diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts
index 1f8da6be9f..78834ba601 100644
--- a/src/services/update-chart.ts
+++ b/src/services/update-chart.ts
@@ -96,6 +96,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> {
 						decCount: 0,
 						decSize: 0
 					}
+				},
+				network: {
+					requests: 0,
+					totalTime: 0,
+					incomingBytes: 0,
+					outgoingBytes: 0
 				}
 			};
 
@@ -161,6 +167,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise<IStats> {
 						decCount: 0,
 						decSize: 0
 					}
+				},
+				network: {
+					requests: 0,
+					totalTime: 0,
+					incomingBytes: 0,
+					outgoingBytes: 0
 				}
 			};
 
@@ -243,3 +255,13 @@ export async function updateDriveStats(file: IDriveFile, isAdditional: boolean)
 
 	await update(inc);
 }
+
+export async function updateNetworkStats(requests: number, time: number, incomingBytes: number, outgoingBytes: number) {
+	const inc = {} as any;
+	inc['network.requests'] = requests;
+	inc['network.totalTime'] = time;
+	inc['network.incomingBytes'] = incomingBytes;
+	inc['network.outgoingBytes'] = outgoingBytes;
+
+	await update(inc);
+}
diff --git a/src/stream.ts b/src/stream.ts
index be7a8c4ba1..ebc75c875c 100644
--- a/src/stream.ts
+++ b/src/stream.ts
@@ -1,58 +1,97 @@
 import * as mongo from 'mongodb';
 import Xev from 'xev';
-
-const ev = new Xev();
+import Meta, { IMeta } from './models/meta';
 
 type ID = string | mongo.ObjectID;
 
-function publish(channel: string, type: string, value?: any): void {
-	const message = type == null ? value : value == null ?
-		{ type: type } :
-		{ type: type, body: value };
+class Publisher {
+	private ev: Xev;
+	private meta: IMeta;
 
-		ev.emit(channel, message);
+	constructor() {
+		this.ev = new Xev();
+
+		setInterval(async () => {
+			this.meta = await Meta.findOne({});
+		}, 5000);
+	}
+
+	public getMeta = async () => {
+		if (this.meta != null) return this.meta;
+
+		this.meta = await Meta.findOne({});
+		return this.meta;
+	}
+
+	private publish = (channel: string, type: string, value?: any): void => {
+		const message = type == null ? value : value == null ?
+			{ type: type } :
+			{ type: type, body: value };
+
+		this.ev.emit(channel, message);
+	}
+
+	public publishUserStream = (userId: ID, type: string, value?: any): void => {
+		this.publish(`user-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishDriveStream = (userId: ID, type: string, value?: any): void => {
+		this.publish(`drive-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishNoteStream = (noteId: ID, type: string): void => {
+		this.publish(`note-stream:${noteId}`, null, noteId);
+	}
+
+	public publishUserListStream = (listId: ID, type: string, value?: any): void => {
+		this.publish(`user-list-stream:${listId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishMessagingStream = (userId: ID, otherpartyId: ID, type: string, value?: any): void => {
+		this.publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishMessagingIndexStream = (userId: ID, type: string, value?: any): void => {
+		this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishReversiStream = (userId: ID, type: string, value?: any): void => {
+		this.publish(`reversi-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishReversiGameStream = (gameId: ID, type: string, value?: any): void => {
+		this.publish(`reversi-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	public publishLocalTimelineStream = async (note: any): Promise<void> => {
+		const meta = await this.getMeta();
+		if (meta.disableLocalTimeline) return;
+		this.publish('local-timeline', null, note);
+	}
+
+	public publishHybridTimelineStream = async (userId: ID, note: any): Promise<void> => {
+		const meta = await this.getMeta();
+		if (meta.disableLocalTimeline) return;
+		this.publish(userId ? `hybrid-timeline:${userId}` : 'hybrid-timeline', null, note);
+	}
+
+	public publishGlobalTimelineStream = (note: any): void => {
+		this.publish('global-timeline', null, note);
+	}
 }
 
-export function publishUserStream(userId: ID, type: string, value?: any): void {
-	publish(`user-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
-}
+const publisher = new Publisher();
 
-export function publishDriveStream(userId: ID, type: string, value?: any): void {
-	publish(`drive-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
-}
+export default publisher;
 
-export function publishNoteStream(noteId: ID, type: string): void {
-	publish(`note-stream:${noteId}`, null, noteId);
-}
-
-export function publishUserListStream(listId: ID, type: string, value?: any): void {
-	publish(`user-list-stream:${listId}`, type, typeof value === 'undefined' ? null : value);
-}
-
-export function publishMessagingStream(userId: ID, otherpartyId: ID, type: string, value?: any): void {
-	publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
-}
-
-export function publishMessagingIndexStream(userId: ID, type: string, value?: any): void {
-	publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
-}
-
-export function publishReversiStream(userId: ID, type: string, value?: any): void {
-	publish(`reversi-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
-}
-
-export function publishReversiGameStream(gameId: ID, type: string, value?: any): void {
-	publish(`reversi-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
-}
-
-export function publishLocalTimelineStream(note: any): void {
-	publish('local-timeline', null, note);
-}
-
-export function publishHybridTimelineStream(userId: ID, note: any): void {
-	publish(userId ? `hybrid-timeline:${userId}` : 'hybrid-timeline', null, note);
-}
-
-export function publishGlobalTimelineStream(note: any): void {
-	publish('global-timeline', null, note);
-}
+export const publishUserStream = publisher.publishUserStream;
+export const publishDriveStream = publisher.publishDriveStream;
+export const publishNoteStream = publisher.publishNoteStream;
+export const publishUserListStream = publisher.publishUserListStream;
+export const publishMessagingStream = publisher.publishMessagingStream;
+export const publishMessagingIndexStream = publisher.publishMessagingIndexStream;
+export const publishReversiStream = publisher.publishReversiStream;
+export const publishReversiGameStream = publisher.publishReversiGameStream;
+export const publishLocalTimelineStream = publisher.publishLocalTimelineStream;
+export const publishHybridTimelineStream = publisher.publishHybridTimelineStream;
+export const publishGlobalTimelineStream = publisher.publishGlobalTimelineStream;
diff --git a/tslint.json b/tslint.json
index ae0df46b96..1adc0a2aed 100644
--- a/tslint.json
+++ b/tslint.json
@@ -17,6 +17,7 @@
 		"no-empty":false,
 		"ordered-imports": [false],
 		"arrow-parens": false,
+		"array-type": false,
 		"object-literal-shorthand": false,
 		"object-literal-key-quotes": false,
 		"triple-equals": [false],
diff --git a/webpack.config.ts b/webpack.config.ts
index 341d4c7022..325923084e 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -7,7 +7,6 @@ import * as webpack from 'webpack';
 import chalk from 'chalk';
 import rndstr from 'rndstr';
 const { VueLoaderPlugin } = require('vue-loader');
-const jsonImporter = require('node-sass-json-importer');
 const minifyHtml = require('html-minifier').minify;
 const WebpackOnBuildPlugin = require('on-build-webpack');
 //const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
@@ -183,22 +182,6 @@ module.exports = {
 					loader: 'stylus-loader'
 				}]
 			}]
-		}, {
-			test: /\.scss$/,
-			exclude: /node_modules/,
-			use: [{
-				loader: 'style-loader'
-			}, {
-				loader: 'css-loader',
-				options: {
-					minimize: true
-				}
-			}, {
-				loader: 'sass-loader',
-				options: {
-					importer: jsonImporter,
-				}
-			}]
 		}, {
 			test: /\.css$/,
 			use: [{