From e6c4ba81334b5968c9b53c91485cf8a3c00963a3 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 18 May 2018 15:42:42 +0900
Subject: [PATCH 01/10] Fix bug

---
 package.json                                               | 4 ++--
 src/client/app/desktop/views/components/user-preview.vue   | 2 +-
 src/client/app/desktop/views/pages/user/user.header.vue    | 2 +-
 src/client/app/mobile/views/components/note-detail.sub.vue | 2 +-
 src/client/app/mobile/views/components/note-detail.vue     | 2 +-
 src/client/app/mobile/views/components/note-preview.vue    | 2 +-
 src/client/app/mobile/views/components/note.sub.vue        | 2 +-
 src/client/app/mobile/views/components/note.vue            | 2 +-
 src/client/app/mobile/views/components/user-card.vue       | 2 +-
 src/client/app/mobile/views/components/user-preview.vue    | 2 +-
 src/client/app/mobile/views/pages/user.vue                 | 2 +-
 11 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/package.json b/package.json
index 6b7f63d6c..35f623922 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
 	"name": "misskey",
 	"author": "syuilo <i@syuilo.com>",
-	"version": "2.10.0",
-	"clientVersion": "1.0.5406",
+	"version": "2.10.1",
+	"clientVersion": "1.0.5407",
 	"codename": "nighthike",
 	"main": "./built/index.js",
 	"private": true,
diff --git a/src/client/app/desktop/views/components/user-preview.vue b/src/client/app/desktop/views/components/user-preview.vue
index f40e60dff..b8854a803 100644
--- a/src/client/app/desktop/views/components/user-preview.vue
+++ b/src/client/app/desktop/views/components/user-preview.vue
@@ -5,7 +5,7 @@
 		<mk-avatar class="avatar" :user="u" :disable-preview="true"/>
 		<div class="title">
 			<router-link class="name" :to="u | userPage">{{ u | userName }}</router-link>
-			<p class="username">@{{ u | acct }}</p>
+			<p class="username"><mk-acct :user="u"/></p>
 		</div>
 		<div class="description">{{ u.description }}</div>
 		<div class="status">
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 edb248dac..73af3a4a4 100644
--- a/src/client/app/desktop/views/pages/user/user.header.vue
+++ b/src/client/app/desktop/views/pages/user/user.header.vue
@@ -10,7 +10,7 @@
 		<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
 		<div class="title">
 			<p class="name">{{ user | userName }}</p>
-			<p class="username">@{{ user | acct }}</p>
+			<p class="username"><mk-acct :user="user"/></p>
 			<p class="location" v-if="user.host === null && user.profile.location">%fa:map-marker%{{ user.profile.location }}</p>
 		</div>
 		<footer>
diff --git a/src/client/app/mobile/views/components/note-detail.sub.vue b/src/client/app/mobile/views/components/note-detail.sub.vue
index e515fda8a..3ad5af171 100644
--- a/src/client/app/mobile/views/components/note-detail.sub.vue
+++ b/src/client/app/mobile/views/components/note-detail.sub.vue
@@ -4,7 +4,7 @@
 	<div class="main">
 		<header>
 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
-			<span class="username">@{{ note.user | acct }}</span>
+			<span class="username"><mk-acct :user="note.user"/></span>
 			<router-link class="time" :to="note | notePage">
 				<mk-time :time="note.createdAt"/>
 			</router-link>
diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue
index 5a7226faa..c6664a91d 100644
--- a/src/client/app/mobile/views/components/note-detail.vue
+++ b/src/client/app/mobile/views/components/note-detail.vue
@@ -25,7 +25,7 @@
 			<mk-avatar class="avatar" :user="p.user"/>
 			<div>
 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
-				<span class="username">@{{ p.user | acct }}</span>
+				<span class="username"><mk-acct :user="p.user"/></span>
 			</div>
 		</header>
 		<div class="body">
diff --git a/src/client/app/mobile/views/components/note-preview.vue b/src/client/app/mobile/views/components/note-preview.vue
index b3ab088ff..b55cad792 100644
--- a/src/client/app/mobile/views/components/note-preview.vue
+++ b/src/client/app/mobile/views/components/note-preview.vue
@@ -4,7 +4,7 @@
 	<div class="main">
 		<header>
 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
-			<span class="username">@{{ note.user | acct }}</span>
+			<span class="username"><mk-acct :user="note.user"/></span>
 			<router-link class="time" :to="note | notePage">
 				<mk-time :time="note.createdAt"/>
 			</router-link>
diff --git a/src/client/app/mobile/views/components/note.sub.vue b/src/client/app/mobile/views/components/note.sub.vue
index 82025291d..2fb3b2ffc 100644
--- a/src/client/app/mobile/views/components/note.sub.vue
+++ b/src/client/app/mobile/views/components/note.sub.vue
@@ -4,7 +4,7 @@
 	<div class="main">
 		<header>
 			<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
-			<span class="username">@{{ note.user | acct }}</span>
+			<span class="username"><mk-acct :user="note.user"/></span>
 			<div class="info">
 				<span class="mobile" v-if="note.viaMobile">%fa:mobile-alt%</span>
 				<router-link class="created-at" :to="note | notePage">
diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue
index 77a766f32..8181bab0a 100644
--- a/src/client/app/mobile/views/components/note.vue
+++ b/src/client/app/mobile/views/components/note.vue
@@ -17,7 +17,7 @@
 			<header>
 				<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
 				<span class="is-bot" v-if="p.user.host === null && p.user.isBot">bot</span>
-				<span class="username">@{{ p.user | acct }}</span>
+				<span class="username"><mk-acct :user="p.user"/></span>
 				<div class="info">
 					<span class="mobile" v-if="p.viaMobile">%fa:mobile-alt%</span>
 					<router-link class="created-at" :to="p | notePage">
diff --git a/src/client/app/mobile/views/components/user-card.vue b/src/client/app/mobile/views/components/user-card.vue
index 432560a54..52c82115b 100644
--- a/src/client/app/mobile/views/components/user-card.vue
+++ b/src/client/app/mobile/views/components/user-card.vue
@@ -6,7 +6,7 @@
 		</a>
 	</header>
 	<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a>
-	<p class="username">@{{ user | acct }}</p>
+	<p class="username"><mk-acct :user="user"/></p>
 	<mk-follow-button :user="user"/>
 </div>
 </template>
diff --git a/src/client/app/mobile/views/components/user-preview.vue b/src/client/app/mobile/views/components/user-preview.vue
index d25836091..a165e66a9 100644
--- a/src/client/app/mobile/views/components/user-preview.vue
+++ b/src/client/app/mobile/views/components/user-preview.vue
@@ -4,7 +4,7 @@
 	<div class="main">
 		<header>
 			<router-link class="name" :to="user | userPage">{{ user | userName }}</router-link>
-			<span class="username">@{{ user | acct }}</span>
+			<span class="username"><mk-acct :user="user"/></span>
 		</header>
 		<div class="body">
 			<div class="description">{{ user.description }}</div>
diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue
index f43454f9d..34adeb03c 100644
--- a/src/client/app/mobile/views/pages/user.vue
+++ b/src/client/app/mobile/views/pages/user.vue
@@ -15,7 +15,7 @@
 				</div>
 				<div class="title">
 					<h1>{{ user | userName }}</h1>
-					<span class="username">@{{ user | acct }}</span>
+					<span class="username"><mk-acct :user="user"/></span>
 					<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
 				</div>
 				<div class="description">{{ user.description }}</div>

From 565d61674d0865810372c4c2cf73f10d7df00adf Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 18 May 2018 16:52:31 +0900
Subject: [PATCH 02/10] Update dependecy :rocket:

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 35f623922..9464d2d73 100644
--- a/package.json
+++ b/package.json
@@ -186,7 +186,7 @@
 		"style-loader": "0.21.0",
 		"stylus": "0.54.5",
 		"stylus-loader": "3.0.2",
-		"summaly": "2.0.4",
+		"summaly": "2.0.6",
 		"swagger-jsdoc": "1.9.7",
 		"syuilo-password-strength": "0.0.1",
 		"tcp-port-used": "0.1.2",

From cc486a3313096314c583547c91c0df1a2adad950 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= <me@m4sk.in>
Date: Fri, 18 May 2018 20:33:55 +0200
Subject: [PATCH 03/10] even moar i18n
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
---
 locales/ja.yml                                | 149 ++++++++++++++++++
 .../views/components/activity.chart.vue       |   2 +-
 .../choose-file-from-drive-window.vue         |  11 +-
 .../choose-folder-from-drive-window.vue       |   6 +-
 .../desktop/views/components/crop-window.vue  |   6 +-
 .../views/components/follow-button.vue        |   6 +-
 .../views/components/followers-window.vue     |   2 +-
 .../desktop/views/components/followers.vue    |   2 +-
 .../views/components/following-window.vue     |   2 +-
 .../desktop/views/components/following.vue    |   2 +-
 .../views/components/friends-maker.vue        |  10 +-
 .../desktop/views/components/game-window.vue  |   2 +-
 .../desktop/views/components/input-dialog.vue |   4 +-
 .../app/desktop/views/components/mentions.vue |   8 +-
 .../components/messaging-room-window.vue      |   2 +-
 .../views/components/note-detail.sub.vue      |   2 +-
 .../app/desktop/views/components/notes.vue    |   4 +-
 .../views/components/progress-dialog.vue      |   2 +-
 .../app/desktop/views/components/settings.vue | 138 ++++++++--------
 .../desktop/views/components/taskmanager.vue  |   2 +-
 20 files changed, 255 insertions(+), 107 deletions(-)

diff --git a/locales/ja.yml b/locales/ja.yml
index d71251d20..5040c135d 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -191,6 +191,12 @@ common/views/widgets/server.vue:
   title: "サーバー情報"
   toggle: "表示を切り替え"
 
+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: "表示を切り替え"
@@ -201,6 +207,23 @@ desktop/views/components/calendar.vue:
   next: "次の月"
   go: "クリックして時間遡行"
 
+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: "ドライブ"
@@ -257,6 +280,32 @@ desktop/views/components/drive.vue:
     upload: "ファイルをアップロード"
     url-upload: "URLからアップロード"
 
+desktop/views/components/follow-button.vue:
+  unfollow: "フォロー解除"
+  follow: "フォローする"
+
+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: "ウィジェットを追加:"
@@ -283,9 +332,25 @@ desktop/views/components/home.vue:
   tips: "ヒント"
   add: "追加"
 
+desktop/views/input-dialog.vue:
+  cancel: "キャンセル"
+  ok: "決定"
+
+desktop/views/components/mentions.vue:
+  all: "すべて"
+  followed: "フォロー中"
+  empty: "あなた宛ての投稿はありません。"
+  empty-followed: "あなたがフォローしているユーザーからの言及はありません。"
+
+desktop/views/components/messaging-room-window.vue:
+  title: "メッセージ:"
+
 desktop/views/components/messaging-window.vue:
   title: "メッセージ"
 
+desktop/views/components/note-detail.sub.vue:
+  private: "(この投稿は非公開です)"
+
 desktop/views/components/notes.note.vue:
   reposted-by: "{}がRenote"
   reply: "返信"
@@ -293,6 +358,10 @@ desktop/views/components/notes.note.vue:
   add-reaction: "リアクション"
   detail: "詳細"
 
+desktop/views/components/notes.vue:
+  error: "読み込みに失敗しました。"
+  retry: "リトライ"
+
 desktop/views/components/notifications.vue:
   more: "もっと見る"
   empty: "ありません!"
@@ -324,6 +393,9 @@ desktop/views/components/post-form-window.vue:
   attaches: "添付: {}メディア"
   uploading-media: "{}個のメディアをアップロード中"
 
+desktop/views/components/progress-dialog.vue:
+  waiting: "待機中"
+
 desktop/views/components/renote-form.vue:
   quote: "引用する..."
   cancel: "キャンセル"
@@ -348,6 +420,80 @@ desktop/views/components/settings.vue:
   other: "その他"
   license: "ライセンス"
 
+  behaviour: "動作"
+  fetch-on-scroll: "スクロールで自動読み込み"
+  fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
+  auto-popout: "ウィンドウの自動ポップアウト"
+  auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
+  advanced: "詳細設定"
+  api-via-stream: "ストリームを経由したAPIリクエスト"
+  api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
+
+  display: "デザインと表示"
+  customize: "ホームをカスタマイズ"
+  dark-mode: "ダークモード"
+  circle-icons: "円形のアイコンを使用"
+  gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
+  post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
+  show-reply-target: "リプライ先を表示する"
+  show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
+  show-renoted-my-notes: "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: "この設定はブラウザに記憶されます。"
+  use-raw-script: "生のスクリプトを読み込む"
+  use-raw-script-desc: "圧縮されていない「生の」スクリプトを使用します。サイズが大きいため、読み込みに時間がかかる場合があります。この設定はブラウザに記憶されます。"
+  source-info: "Misskeyはソースマップも提供しています。"
+  experimental: "実験的機能を有効にする"
+  experimental-desc: "実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。"
+  tools: "ツール"
+  task-manager: "タスクマネージャ"
+  third-parties: "サードパーティ"
+
 desktop/views/components/settings.2fa.vue:
   intro: "二段階認証を設定すると、サインイン時にパスワードだけでなく、予め登録しておいた物理的なデバイス(例えばあなたのスマートフォンなど)も必要になり、よりセキュリティが向上します。"
   detail: "詳細..."
@@ -398,6 +544,9 @@ desktop/views/components/settings.profile.vue:
   birthday: "誕生日"
   save: "保存"
 
+desktop/views/components/taskmanager.vue:
+  title: "タスクマネージャ"
+
 desktop/views/components/timeline.vue:
   home: "ホーム"
   local: "ローカル"
diff --git a/src/client/app/desktop/views/components/activity.chart.vue b/src/client/app/desktop/views/components/activity.chart.vue
index 175c5d37e..ff489d988 100644
--- a/src/client/app/desktop/views/components/activity.chart.vue
+++ b/src/client/app/desktop/views/components/activity.chart.vue
@@ -1,6 +1,6 @@
 <template>
 <svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" preserveAspectRatio="none" @mousedown.prevent="onMousedown">
-	<title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title>
+	<title>%i18n:@total%<br/>%i18n:@notes%<br/>%i18n:@replies%<br/>%i18n:@renotes%</title>
 	<polyline
 		:points="pointsNote"
 		fill="none"
diff --git a/src/client/app/desktop/views/components/choose-file-from-drive-window.vue b/src/client/app/desktop/views/components/choose-file-from-drive-window.vue
index 9a1e9c958..30e59429d 100644
--- a/src/client/app/desktop/views/components/choose-file-from-drive-window.vue
+++ b/src/client/app/desktop/views/components/choose-file-from-drive-window.vue
@@ -2,7 +2,7 @@
 <mk-window ref="window" is-modal width="800px" height="500px" @closed="$destroy">
 	<span slot="header">
 		<span v-html="title" :class="$style.title"></span>
-		<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}ファイル選択中)</span>
+		<span :class="$style.count" v-if="multiple && files.length > 0">({{ files.length }}%i18n:@choose-file%)</span>
 	</span>
 
 	<mk-drive
@@ -13,9 +13,9 @@
 		@change-selection="onChangeSelection"
 	/>
 	<div :class="$style.footer">
-		<button :class="$style.upload" title="PCからドライブにファイルをアップロード" @click="upload">%fa:upload%</button>
-		<button :class="$style.cancel" @click="cancel">キャンセル</button>
-		<button :class="$style.ok" :disabled="multiple && files.length == 0" @click="ok">決定</button>
+		<button :class="$style.upload" title="%i18n:@upload%" @click="upload">%fa:upload%</button>
+		<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
+		<button :class="$style.ok" :disabled="multiple && files.length == 0" @click="ok">%i18n:@ok%</button>
 	</div>
 </mk-window>
 </template>
@@ -28,7 +28,7 @@ export default Vue.extend({
 			default: false
 		},
 		title: {
-			default: '%fa:R file%ファイルを選択'
+			default: '%fa:R file%%i18n:@choose-prompt%s'
 		}
 	},
 	data() {
@@ -177,4 +177,3 @@ export default Vue.extend({
 		border-color #dcdcdc
 
 </style>
-
diff --git a/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue b/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue
index f99533176..0c4643fdc 100644
--- a/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue
+++ b/src/client/app/desktop/views/components/choose-folder-from-drive-window.vue
@@ -10,8 +10,8 @@
 		:multiple="false"
 	/>
 	<div :class="$style.footer">
-		<button :class="$style.cancel" @click="cancel">キャンセル</button>
-		<button :class="$style.ok" @click="ok">決定</button>
+		<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
+		<button :class="$style.ok" @click="ok">%i18n:@ok%</button>
 	</div>
 </mk-window>
 </template>
@@ -21,7 +21,7 @@ import Vue from 'vue';
 export default Vue.extend({
 	props: {
 		title: {
-			default: '%fa:R folder%フォルダを選択'
+			default: '%fa:R folder%%i18n:@choose-prompt%'
 		}
 	},
 	methods: {
diff --git a/src/client/app/desktop/views/components/crop-window.vue b/src/client/app/desktop/views/components/crop-window.vue
index eb6a55d95..4fa258549 100644
--- a/src/client/app/desktop/views/components/crop-window.vue
+++ b/src/client/app/desktop/views/components/crop-window.vue
@@ -10,9 +10,9 @@
 			/>
 		</div>
 		<div :class="$style.actions">
-			<button :class="$style.skip" @click="skip">クロップをスキップ</button>
-			<button :class="$style.cancel" @click="cancel">キャンセル</button>
-			<button :class="$style.ok" @click="ok">決定</button>
+			<button :class="$style.skip" @click="skip">%i18n:@skip%</button>
+			<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
+			<button :class="$style.ok" @click="ok">%i18n:@ok%</button>
 		</div>
 	</mk-window>
 </template>
diff --git a/src/client/app/desktop/views/components/follow-button.vue b/src/client/app/desktop/views/components/follow-button.vue
index 60c6129f6..dae760495 100644
--- a/src/client/app/desktop/views/components/follow-button.vue
+++ b/src/client/app/desktop/views/components/follow-button.vue
@@ -3,15 +3,15 @@
 	:class="{ wait, follow: !user.isFollowing, unfollow: user.isFollowing, big: size == 'big' }"
 	@click="onClick"
 	:disabled="wait"
-	:title="user.isFollowing ? 'フォロー解除' : 'フォローする'"
+	:title="user.isFollowing ? '%i18n:@unfollow%' : '%i18n:@follow%'"
 >
 	<template v-if="!wait && user.isFollowing">
 		<template v-if="size == 'compact'">%fa:minus%</template>
-		<template v-if="size == 'big'">%fa:minus%フォロー解除</template>
+		<template v-if="size == 'big'">%fa:minus%%i18n:@unfollow%</template>
 	</template>
 	<template v-if="!wait && !user.isFollowing">
 		<template v-if="size == 'compact'">%fa:plus%</template>
-		<template v-if="size == 'big'">%fa:plus%フォロー</template>
+		<template v-if="size == 'big'">%fa:plus%%i18n:@follow%</template>
 	</template>
 	<template v-if="wait">%fa:spinner .pulse .fw%</template>
 </button>
diff --git a/src/client/app/desktop/views/components/followers-window.vue b/src/client/app/desktop/views/components/followers-window.vue
index 16206299d..f3eec13e0 100644
--- a/src/client/app/desktop/views/components/followers-window.vue
+++ b/src/client/app/desktop/views/components/followers-window.vue
@@ -1,7 +1,7 @@
 <template>
 <mk-window width="400px" height="550px" @closed="$destroy">
 	<span slot="header" :class="$style.header">
-		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user | userName }}のフォロワー
+		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>%i18n:!@followers%.replace('{}', {{ user | userName }})
 	</span>
 	<mk-followers :user="user"/>
 </mk-window>
diff --git a/src/client/app/desktop/views/components/followers.vue b/src/client/app/desktop/views/components/followers.vue
index a1b98995d..1ef9f6977 100644
--- a/src/client/app/desktop/views/components/followers.vue
+++ b/src/client/app/desktop/views/components/followers.vue
@@ -4,7 +4,7 @@
 	:count="user.followersCount"
 	:you-know-count="user.followersYouKnowCount"
 >
-	フォロワーはいないようです。
+	%i18n:@empty%
 </mk-users-list>
 </template>
 
diff --git a/src/client/app/desktop/views/components/following-window.vue b/src/client/app/desktop/views/components/following-window.vue
index cc3d77198..153819b12 100644
--- a/src/client/app/desktop/views/components/following-window.vue
+++ b/src/client/app/desktop/views/components/following-window.vue
@@ -1,7 +1,7 @@
 <template>
 <mk-window width="400px" height="550px" @closed="$destroy">
 	<span slot="header" :class="$style.header">
-		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>{{ user | userName }}のフォロー
+		<img :src="`${user.avatarUrl}?thumbnail&size=64`" alt=""/>%i18n:!@following%.replace('{}', {{ user | userName }})
 	</span>
 	<mk-following :user="user"/>
 </mk-window>
diff --git a/src/client/app/desktop/views/components/following.vue b/src/client/app/desktop/views/components/following.vue
index b7aedda84..d55ce1c0d 100644
--- a/src/client/app/desktop/views/components/following.vue
+++ b/src/client/app/desktop/views/components/following.vue
@@ -4,7 +4,7 @@
 	:count="user.followingCount"
 	:you-know-count="user.followingYouKnowCount"
 >
-	フォロー中のユーザーはいないようです。
+	%i18n:@empty%
 </mk-users-list>
 </template>
 
diff --git a/src/client/app/desktop/views/components/friends-maker.vue b/src/client/app/desktop/views/components/friends-maker.vue
index 3c1f8b825..7dfd9e435 100644
--- a/src/client/app/desktop/views/components/friends-maker.vue
+++ b/src/client/app/desktop/views/components/friends-maker.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="mk-friends-maker">
-	<p class="title">気になるユーザーをフォロー:</p>
+	<p class="title">%i18n:@title%</p>
 	<div class="users" v-if="!fetching && users.length > 0">
 		<div class="user" v-for="user in users" :key="user.id">
 			<mk-avatar class="avatar" :user="user" target="_blank"/>
@@ -11,10 +11,10 @@
 			<mk-follow-button :user="user"/>
 		</div>
 	</div>
-	<p class="empty" v-if="!fetching && users.length == 0">おすすめのユーザーは見つかりませんでした。</p>
-	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%読み込んでいます<mk-ellipsis/></p>
-	<a class="refresh" @click="refresh">もっと見る</a>
-	<button class="close" @click="$destroy()" title="閉じる">%fa:times%</button>
+	<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>
 </div>
 </template>
 
diff --git a/src/client/app/desktop/views/components/game-window.vue b/src/client/app/desktop/views/components/game-window.vue
index 3c8bf40e1..c3c0f9007 100644
--- a/src/client/app/desktop/views/components/game-window.vue
+++ b/src/client/app/desktop/views/components/game-window.vue
@@ -1,6 +1,6 @@
 <template>
 <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
-	<span slot="header" :class="$style.header">%fa:gamepad%オセロ</span>
+	<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
 	<mk-othello :class="$style.content" @gamed="g => game = g"/>
 </mk-window>
 </template>
diff --git a/src/client/app/desktop/views/components/input-dialog.vue b/src/client/app/desktop/views/components/input-dialog.vue
index e939fc190..e2cf4e48f 100644
--- a/src/client/app/desktop/views/components/input-dialog.vue
+++ b/src/client/app/desktop/views/components/input-dialog.vue
@@ -8,8 +8,8 @@
 		<input ref="text" v-model="text" :type="type" @keydown="onKeydown" :placeholder="placeholder"/>
 	</div>
 	<div :class="$style.actions">
-		<button :class="$style.cancel" @click="cancel">キャンセル</button>
-		<button :class="$style.ok" :disabled="!allowEmpty && text.length == 0" @click="ok">決定</button>
+		<button :class="$style.cancel" @click="cancel">%i18n:@cancel%</button>
+		<button :class="$style.ok" :disabled="!allowEmpty && text.length == 0" @click="ok">%i18n:@ok%</button>
 	</div>
 </mk-window>
 </template>
diff --git a/src/client/app/desktop/views/components/mentions.vue b/src/client/app/desktop/views/components/mentions.vue
index 66bdab5c0..23fb7cccd 100644
--- a/src/client/app/desktop/views/components/mentions.vue
+++ b/src/client/app/desktop/views/components/mentions.vue
@@ -1,16 +1,16 @@
 <template>
 <div class="mk-mentions">
 	<header>
-		<span :data-active="mode == 'all'" @click="mode = 'all'">すべて</span>
-		<span :data-active="mode == 'following'" @click="mode = 'following'">フォロー中</span>
+		<span :data-active="mode == 'all'" @click="mode = 'all'">%i18n:@all%</span>
+		<span :data-active="mode == 'following'" @click="mode = 'following'">%i18n:@followed%</span>
 	</header>
 	<div class="fetching" v-if="fetching">
 		<mk-ellipsis-icon/>
 	</div>
 	<p class="empty" v-if="notes.length == 0 && !fetching">
 		%fa:R comments%
-		<span v-if="mode == 'all'">あなた宛ての投稿はありません。</span>
-		<span v-if="mode == 'following'">あなたがフォローしているユーザーからの言及はありません。</span>
+		<span v-if="mode == 'all'">%i18n:@empty%</span>
+		<span v-if="mode == 'following'">%i18n:@empty-followed%</span>
 	</p>
 	<mk-notes :notes="notes" ref="timeline"/>
 </div>
diff --git a/src/client/app/desktop/views/components/messaging-room-window.vue b/src/client/app/desktop/views/components/messaging-room-window.vue
index dbe326673..cbb58b5e9 100644
--- a/src/client/app/desktop/views/components/messaging-room-window.vue
+++ b/src/client/app/desktop/views/components/messaging-room-window.vue
@@ -1,6 +1,6 @@
 <template>
 <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
-	<span slot="header" :class="$style.header">%fa:comments%メッセージ: {{ user | userName }}</span>
+	<span slot="header" :class="$style.header">%fa:comments%%i18n:@title% {{ user | userName }}</span>
 	<mk-messaging-room :user="user" :class="$style.content"/>
 </mk-window>
 </template>
diff --git a/src/client/app/desktop/views/components/note-detail.sub.vue b/src/client/app/desktop/views/components/note-detail.sub.vue
index 32119da50..c798b41b2 100644
--- a/src/client/app/desktop/views/components/note-detail.sub.vue
+++ b/src/client/app/desktop/views/components/note-detail.sub.vue
@@ -15,7 +15,7 @@
 		</header>
 		<div class="body">
 			<div class="text">
-				<span v-if="note.isHidden" style="opacity: 0.5">(この投稿は非公開です)</span>
+				<span v-if="note.isHidden" style="opacity: 0.5">%i18n:@private%</span>
 				<mk-note-html v-if="note.text" :text="note.text" :i="os.i"/>
 			</div>
 			<div class="media" v-if="note.mediaIds.length > 0">
diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue
index 7e80e6f74..c041e5278 100644
--- a/src/client/app/desktop/views/components/notes.vue
+++ b/src/client/app/desktop/views/components/notes.vue
@@ -5,8 +5,8 @@
 	<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot>
 
 	<div v-if="!fetching && requestInitPromise != null">
-		<p>読み込みに失敗しました。</p>
-		<button @click="resolveInitPromise">リトライ</button>
+		<p>%i18n:@error%</p>
+		<button @click="resolveInitPromise">%i18n:@retry%</button>
 	</div>
 
 	<transition-group name="mk-notes" class="transition">
diff --git a/src/client/app/desktop/views/components/progress-dialog.vue b/src/client/app/desktop/views/components/progress-dialog.vue
index a4292e1ae..2f59733d9 100644
--- a/src/client/app/desktop/views/components/progress-dialog.vue
+++ b/src/client/app/desktop/views/components/progress-dialog.vue
@@ -2,7 +2,7 @@
 <mk-window ref="window" :is-modal="false" :can-close="false" width="500px" @closed="$destroy">
 	<span slot="header">{{ title }}<mk-ellipsis/></span>
 	<div :class="$style.body">
-		<p :class="$style.init" v-if="isNaN(value)">待機中<mk-ellipsis/></p>
+		<p :class="$style.init" v-if="isNaN(value)">%i18n:@waiting%<mk-ellipsis/></p>
 		<p :class="$style.percentage" v-if="!isNaN(value)">{{ Math.floor((value / max) * 100) }}</p>
 		<progress :class="$style.progress"
 			v-if="!isNaN(value) && value < max"
diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue
index 4e5e281fd..9510752f7 100644
--- a/src/client/app/desktop/views/components/settings.vue
+++ b/src/client/app/desktop/views/components/settings.vue
@@ -19,91 +19,91 @@
 		</section>
 
 		<section class="web" v-show="page == 'web'">
-			<h1>動作</h1>
-			<mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み">
-				<span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。</span>
+			<h1>%i18n:@behaviour%</h1>
+			<mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="%i18n:@fetch-on-scroll%>
+				<span>%i18n:@fetch-on-scroll-desc%</span>
 			</mk-switch>
-			<mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト">
-				<span>ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。</span>
+			<mk-switch v-model="autoPopout" text="%i18n:@auto-popout%">
+				<span>%i18n:@auto-popout-desc%</span>
 			</mk-switch>
 			<details>
-				<summary>詳細設定</summary>
-				<mk-switch v-model="apiViaStream" text="ストリームを経由したAPIリクエスト">
-					<span>この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。</span>
+				<summary>%i18n:@advanced%</summary>
+				<mk-switch v-model="apiViaStream" text="%i18n:@api-via-stream%">
+					<span>%i18n:@api-via-stream-desc%</span>
 				</mk-switch>
 			</details>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
-			<h1>デザインと表示</h1>
+			<h1>%i18n:@display%</h1>
 			<div class="div">
-				<button class="ui button" @click="customizeHome" style="margin-bottom: 16px">ホームをカスタマイズ</button>
+				<button class="ui button" @click="customizeHome" style="margin-bottom: 16px">%i18n:@customize%</button>
 			</div>
 			<div class="div">
-				<mk-switch v-model="darkmode" text="ダークモード"/>
-				<mk-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons" text="円形のアイコンを使用"/>
-				<mk-switch v-model="clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/>
+				<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
+				<mk-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
+				<mk-switch v-model="clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
 			</div>
-			<mk-switch v-model="clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/>
-			<mk-switch v-model="clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="リプライ先を表示する"/>
-			<mk-switch v-model="clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="自分の行ったRenoteをタイムラインに表示する"/>
-			<mk-switch v-model="clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="Renoteされた自分の投稿をタイムラインに表示する"/>
-			<mk-switch v-model="clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開">
-				<span>位置情報が添付された投稿のマップを自動的に展開します。</span>
+			<mk-switch v-model="clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
+			<mk-switch v-model="clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="%i18n:@show-reply-target%"/>
+			<mk-switch v-model="clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="%i18n:@show-my-renotes%"/>
+			<mk-switch v-model="clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/>
+			<mk-switch v-model="clientSettings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%">
+				<span>%i18n:@show-maps-desc%</span>
 			</mk-switch>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
-			<h1>サウンド</h1>
-			<mk-switch v-model="enableSounds" text="サウンドを有効にする">
-				<span>投稿やメッセージを送受信したときなどにサウンドを再生します。この設定はブラウザに記憶されます。</span>
+			<h1>%i18n:@sound%</h1>
+			<mk-switch v-model="enableSounds" text="%i18n:@enable-sounds%">
+				<span>%i18n:@enable-sounds-desc%</span>
 			</mk-switch>
-			<label>ボリューム</label>
+			<label>%i18n:@volume%</label>
 			<el-slider
 				v-model="soundVolume"
 				:show-input="true"
 				:format-tooltip="v => `${v}%`"
 				:disabled="!enableSounds"
 			/>
-			<button class="ui button" @click="soundTest">%fa:volume-up% テスト</button>
+			<button class="ui button" @click="soundTest">%fa:volume-up% %i18n:@test%</button>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
-			<h1>モバイル</h1>
-			<mk-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/>
+			<h1>%i18n:@mobile%</h1>
+			<mk-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="%i18n:@disable-via-mobile%"/>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
-			<h1>言語</h1>
-			<el-select v-model="lang" placeholder="言語を選択">
-				<el-option-group label="推奨">
-					<el-option label="自動" value=""/>
+			<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=""/>
 				</el-option-group>
-				<el-option-group label="言語を指定">
-					<el-option label="ja" value="ja"/>
-					<el-option label="en" value="en"/>
-					<el-option label="fr" value="fr"/>
-					<el-option label="pl" value="pl"/>
-					<el-option label="de" value="de"/>
+				<el-option-group label="%i18n:@specify-language%">
+					<el-option label="日本語" value="ja"/>
+					<el-option label="English" value="en"/>
+					<el-option label="Français" value="fr"/>
+					<el-option label="Polski" value="pl"/>
+					<el-option label="Deutsch" value="de"/>
 				</el-option-group>
 			</el-select>
 			<div class="none ui info">
-				<p>%fa:info-circle%変更はページの再度読み込み後に反映されます。</p>
+				<p>%fa:info-circle%%i18n:@language-desc%</p>
 			</div>
 		</section>
 
 		<section class="web" v-show="page == 'web'">
-			<h1>キャッシュ</h1>
-			<button class="ui button" @click="clean">クリーンアップ</button>
+			<h1>%i18n:@cache%</h1>
+			<button class="ui button" @click="clean">%i18n:@clean-cache%</button>
 			<div class="none ui info warn">
-				<p>%fa:exclamation-triangle%クリーンアップを行うと、ブラウザに記憶されたアカウント情報のキャッシュ、書きかけの投稿・返信・メッセージ、およびその他のデータ(設定情報含む)が削除されます。クリーンアップを行った後はページを再度読み込みする必要があります。</p>
+				<p>%fa:exclamation-triangle%%i18n:@cache-warn%</p>
 			</div>
 		</section>
 
 		<section class="notification" v-show="page == 'notification'">
 			<h1>%i18n:@notification%</h1>
-			<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="投稿の自動ウォッチ">
-				<span>リアクションしたり返信したりした投稿に関する通知を自動的に受け取るようにします。</span>
+			<mk-switch v-model="os.i.settings.autoWatch" @change="onChangeAutoWatch" text="%i18n:@auto-watch%">
+				<span>%i18n:@auto-watch-desc%</span>
 			</mk-switch>
 		</section>
 
@@ -148,57 +148,57 @@
 		</section>
 
 		<section class="other" v-show="page == 'other'">
-			<h1>Misskeyについて</h1>
-			<p v-if="meta">このサーバーの運営者: <i><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></i></p>
+			<h1>%i18n:@about%</h1>
+			<p v-if="meta">%i18n:@operator%: <i><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></i></p>
 		</section>
 
 		<section class="other" v-show="page == 'other'">
-			<h1>Misskey Update</h1>
+			<h1>%i18n:@update%</h1>
 			<p>
-				<span>バージョン: <i>{{ version }}</i></span>
+				<span>%i18n:@version% <i>{{ version }}</i></span>
 				<template v-if="latestVersion !== undefined">
 					<br>
-					<span>最新のバージョン: <i>{{ latestVersion ? latestVersion : version }}</i></span>
+					<span>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></span>
 				</template>
 			</p>
 			<button class="ui button block" @click="checkForUpdate" :disabled="checkingForUpdate">
-				<template v-if="checkingForUpdate">アップデートを確認中<mk-ellipsis/></template>
-				<template v-else>アップデートを確認</template>
+				<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
+				<template v-else>%i18n:@do-update%</template>
 			</button>
 			<details>
-				<summary>詳細設定</summary>
-				<mk-switch v-model="preventUpdate" text="アップデートを延期する(非推奨)">
-					<span>この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。</span>
+				<summary>%i18n:@update-settings%</summary>
+				<mk-switch v-model="preventUpdate" text="%i18n:@prevent-update%>
+					<span>%i18n:@prevent-update-desc%</span>
 				</mk-switch>
 			</details>
 		</section>
 
 		<section class="other" v-show="page == 'other'">
-			<h1>高度な設定</h1>
-			<mk-switch v-model="debug" text="デバッグモードを有効にする">
-				<span>この設定はブラウザに記憶されます。</span>
+			<h1>%i18n:@advanced-settings%</h1>
+			<mk-switch v-model="debug" text="%i18n:@debug-mode%">
+				<span>%i18n:@debug-mode-desc%</span>
 			</mk-switch>
 			<template v-if="debug">
-				<mk-switch v-model="useRawScript" text="生のスクリプトを読み込む">
-					<span>圧縮されていない「生の」スクリプトを使用します。サイズが大きいため、読み込みに時間がかかる場合があります。この設定はブラウザに記憶されます。</span>
+				<mk-switch v-model="useRawScript" text="%i18n:@use-raw-script%">
+					<span>%i18n:@use-raw-script-desc%</span>
 				</mk-switch>
 				<div class="none ui info">
-				<p>%fa:info-circle%Misskeyはソースマップも提供しています。</p>
+				<p>%fa:info-circle%%i18n:@source-info%</p>
 			</div>
 			</template>
-			<mk-switch v-model="enableExperimental" text="実験的機能を有効にする">
-				<span>実験的機能を有効にするとMisskeyの動作が不安定になる可能性があります。この設定はブラウザに記憶されます。</span>
+			<mk-switch v-model="enableExperimental" text="%i18n:@experimental%">
+				<span>%i18n:@experimental-desc%</span>
 			</mk-switch>
 			<details v-if="debug">
-				<summary>ツール</summary>
-				<button class="ui button block" @click="taskmngr">タスクマネージャ</button>
+				<summary>%i18n:@tools%</summary>
+				<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
 			</details>
 		</section>
 
 		<section class="other" v-show="page == 'other'">
 			<h1>%i18n:@license%</h1>
 			<div v-html="license"></div>
-			<a :href="licenseUrl" target="_blank">サードパーティ</a>
+			<a :href="licenseUrl" target="_blank">%i18n:@third-parties%</a>
 		</section>
 	</div>
 </div>
@@ -371,13 +371,13 @@ export default Vue.extend({
 				this.latestVersion = newer;
 				if (newer == null) {
 					(this as any).apis.dialog({
-						title: '利用可能な更新はありません',
-						text: 'お使いのMisskeyは最新です。'
+						title: '%i18n:@no-updates%',
+						text: '%i18n:@no-updates-desc%'
 					});
 				} else {
 					(this as any).apis.dialog({
-						title: '新しいバージョンが利用可能です',
-						text: 'ページを再度読み込みすると更新が適用されます。'
+						title: '%i18n:@update-available%',
+						text: '%i18n:@update-available-desc%'
 					});
 				}
 			});
@@ -385,8 +385,8 @@ export default Vue.extend({
 		clean() {
 			localStorage.clear();
 			(this as any).apis.dialog({
-				title: 'キャッシュを削除しました',
-				text: 'ページを再度読み込みしてください。'
+				title: '%i18n:@cache-cleared%',
+				text: '%i18n:@caache-cleared-desc%'
 			});
 		},
 		soundTest() {
diff --git a/src/client/app/desktop/views/components/taskmanager.vue b/src/client/app/desktop/views/components/taskmanager.vue
index a00fabb04..1f1385add 100644
--- a/src/client/app/desktop/views/components/taskmanager.vue
+++ b/src/client/app/desktop/views/components/taskmanager.vue
@@ -1,6 +1,6 @@
 <template>
 <mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
-	<span slot="header" :class="$style.header">%fa:stethoscope%タスクマネージャ</span>
+	<span slot="header" :class="$style.header">%fa:stethoscope%%i18n:@title%</span>
 	<el-tabs :class="$style.content">
 		<el-tab-pane label="Requests">
 			<el-table

From ddad9da1c1dfc8d7129de1b3c1e754544eca82f0 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 07:22:39 +0900
Subject: [PATCH 04/10] Fix

---
 .../app/desktop/views/components/settings.vue    | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue
index 9510752f7..6652a8ac3 100644
--- a/src/client/app/desktop/views/components/settings.vue
+++ b/src/client/app/desktop/views/components/settings.vue
@@ -20,7 +20,7 @@
 
 		<section class="web" v-show="page == 'web'">
 			<h1>%i18n:@behaviour%</h1>
-			<mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="%i18n:@fetch-on-scroll%>
+			<mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="%i18n:@fetch-on-scroll%">
 				<span>%i18n:@fetch-on-scroll-desc%</span>
 			</mk-switch>
 			<mk-switch v-model="autoPopout" text="%i18n:@auto-popout%">
@@ -167,7 +167,7 @@
 			</button>
 			<details>
 				<summary>%i18n:@update-settings%</summary>
-				<mk-switch v-model="preventUpdate" text="%i18n:@prevent-update%>
+				<mk-switch v-model="preventUpdate" text="%i18n:@prevent-update%">
 					<span>%i18n:@prevent-update-desc%</span>
 				</mk-switch>
 			</details>
@@ -371,13 +371,13 @@ export default Vue.extend({
 				this.latestVersion = newer;
 				if (newer == null) {
 					(this as any).apis.dialog({
-						title: '%i18n:@no-updates%',
-						text: '%i18n:@no-updates-desc%'
+						title: '%i18n:!@no-updates%',
+						text: '%i18n:!@no-updates-desc%'
 					});
 				} else {
 					(this as any).apis.dialog({
-						title: '%i18n:@update-available%',
-						text: '%i18n:@update-available-desc%'
+						title: '%i18n:!@update-available%',
+						text: '%i18n:!@update-available-desc%'
 					});
 				}
 			});
@@ -385,8 +385,8 @@ export default Vue.extend({
 		clean() {
 			localStorage.clear();
 			(this as any).apis.dialog({
-				title: '%i18n:@cache-cleared%',
-				text: '%i18n:@caache-cleared-desc%'
+				title: '%i18n:!@cache-cleared%',
+				text: '%i18n:!@caache-cleared-desc%'
 			});
 		},
 		soundTest() {

From e6eb1b2ae1c38b335ff4755d35a33a0ca4450454 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 07:33:34 +0900
Subject: [PATCH 05/10] Clean up

---
 locales/ja.yml                                |  13 -
 src/client/app/ch/script.ts                   |  15 -
 src/client/app/ch/style.styl                  |  10 -
 src/client/app/ch/tags/channel.tag            | 409 ------------------
 src/client/app/ch/tags/header.tag             |  20 -
 src/client/app/ch/tags/index.tag              |  37 --
 src/client/app/ch/tags/index.ts               |   3 -
 .../app/common/scripts/streaming/channel.ts   |  13 -
 .../app/common/views/widgets/access-log.vue   |  91 ----
 src/client/app/common/views/widgets/index.ts  |   2 -
 .../app/desktop/views/components/home.vue     |   2 -
 .../app/desktop/views/components/mentions.vue | 125 ------
 .../desktop/views/components/notes.note.vue   |   6 -
 .../views/widgets/channel.channel.form.vue    |  67 ---
 .../views/widgets/channel.channel.note.vue    |  65 ---
 .../desktop/views/widgets/channel.channel.vue | 106 -----
 .../app/desktop/views/widgets/channel.vue     | 108 -----
 src/client/app/desktop/views/widgets/index.ts |   2 -
 .../app/mobile/views/components/note.vue      |   4 -
 src/client/app/mobile/views/pages/widgets.vue |   1 -
 src/models/channel-watching.ts                |  13 -
 src/models/channel.ts                         |  75 ----
 src/models/note.ts                            |   7 -
 src/publishers/stream.ts                      |   5 -
 src/renderers/get-note-summary.ts             |   3 -
 src/server/api/endpoints.ts                   |  28 +-
 src/server/api/endpoints/channels.ts          |  58 ---
 src/server/api/endpoints/channels/create.ts   |  35 --
 src/server/api/endpoints/channels/notes.ts    |  74 ----
 src/server/api/endpoints/channels/show.ts     |  26 --
 src/server/api/endpoints/channels/unwatch.ts  |  56 ---
 src/server/api/endpoints/channels/watch.ts    |  54 ---
 src/server/api/endpoints/notes/create.ts      |  45 --
 src/server/api/endpoints/notes/timeline.ts    |  33 +-
 src/server/api/stream/channel.ts              |  14 -
 src/server/api/streaming.ts                   |   6 -
 36 files changed, 5 insertions(+), 1626 deletions(-)
 delete mode 100644 src/client/app/ch/script.ts
 delete mode 100644 src/client/app/ch/style.styl
 delete mode 100644 src/client/app/ch/tags/channel.tag
 delete mode 100644 src/client/app/ch/tags/header.tag
 delete mode 100644 src/client/app/ch/tags/index.tag
 delete mode 100644 src/client/app/ch/tags/index.ts
 delete mode 100644 src/client/app/common/scripts/streaming/channel.ts
 delete mode 100644 src/client/app/common/views/widgets/access-log.vue
 delete mode 100644 src/client/app/desktop/views/components/mentions.vue
 delete mode 100644 src/client/app/desktop/views/widgets/channel.channel.form.vue
 delete mode 100644 src/client/app/desktop/views/widgets/channel.channel.note.vue
 delete mode 100644 src/client/app/desktop/views/widgets/channel.channel.vue
 delete mode 100644 src/client/app/desktop/views/widgets/channel.vue
 delete mode 100644 src/models/channel-watching.ts
 delete mode 100644 src/models/channel.ts
 delete mode 100644 src/server/api/endpoints/channels.ts
 delete mode 100644 src/server/api/endpoints/channels/create.ts
 delete mode 100644 src/server/api/endpoints/channels/notes.ts
 delete mode 100644 src/server/api/endpoints/channels/show.ts
 delete mode 100644 src/server/api/endpoints/channels/unwatch.ts
 delete mode 100644 src/server/api/endpoints/channels/watch.ts
 delete mode 100644 src/server/api/stream/channel.ts

diff --git a/locales/ja.yml b/locales/ja.yml
index 5040c135d..4563e9485 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -324,8 +324,6 @@ desktop/views/components/home.vue:
   polls: "投票"
   post-form: "投稿フォーム"
   messaging: "メッセージ"
-  channel: "チャンネル"
-  access-log: "アクセスログ"
   server: "サーバー情報"
   donation: "寄付のお願い"
   nav: "ナビゲーション"
@@ -336,12 +334,6 @@ desktop/views/input-dialog.vue:
   cancel: "キャンセル"
   ok: "決定"
 
-desktop/views/components/mentions.vue:
-  all: "すべて"
-  followed: "フォロー中"
-  empty: "あなた宛ての投稿はありません。"
-  empty-followed: "あなたがフォローしているユーザーからの言及はありません。"
-
 desktop/views/components/messaging-room-window.vue:
   title: "メッセージ:"
 
@@ -660,11 +652,6 @@ desktop/views/widgets/users.vue:
   refresh: "他を見る"
   no-one: "いません!"
 
-desktop/views/widgets/channel.vue:
-  title: "チャンネル"
-  settings: "ウィジェットの設定"
-  get-started: "右上の歯車をクリックして受信するチャンネルを指定してください"
-
 mobile/views/components/drive.vue:
   drive: "ドライブ"
   used: "使用中"
diff --git a/src/client/app/ch/script.ts b/src/client/app/ch/script.ts
deleted file mode 100644
index 4c6b6dfd1..000000000
--- a/src/client/app/ch/script.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Channels
- */
-
-// Style
-import './style.styl';
-
-require('./tags');
-import init from '../init';
-
-/**
- * init
- */
-init(() => {
-});
diff --git a/src/client/app/ch/style.styl b/src/client/app/ch/style.styl
deleted file mode 100644
index 21ca648cb..000000000
--- a/src/client/app/ch/style.styl
+++ /dev/null
@@ -1,10 +0,0 @@
-@import "../app"
-
-html
-	padding 8px
-	background #efefef
-
-#wait
-	top auto
-	bottom 15px
-	left 15px
diff --git a/src/client/app/ch/tags/channel.tag b/src/client/app/ch/tags/channel.tag
deleted file mode 100644
index 74b1a9ba1..000000000
--- a/src/client/app/ch/tags/channel.tag
+++ /dev/null
@@ -1,409 +0,0 @@
-<mk-channel>
-	<mk-header/>
-	<hr>
-	<main v-if="!fetching">
-		<h1>{ channel.title }</h1>
-
-		<div v-if="$root.$data.os.isSignedIn">
-			<p v-if="channel.isWatching">このチャンネルをウォッチしています <a @click="unwatch">ウォッチ解除</a></p>
-			<p v-if="!channel.isWatching"><a @click="watch">このチャンネルをウォッチする</a></p>
-		</div>
-
-		<div class="share">
-			<mk-twitter-button/>
-			<mk-line-button/>
-		</div>
-
-		<div class="body">
-			<p v-if="notesFetching">読み込み中<mk-ellipsis/></p>
-			<div v-if="!notesFetching">
-				<p v-if="notes == null || notes.length == 0">まだ投稿がありません</p>
-				<template v-if="notes != null">
-					<mk-channel-note each={ note in notes.slice().reverse() } note={ note } form={ parent.refs.form }/>
-				</template>
-			</div>
-		</div>
-		<hr>
-		<mk-channel-form v-if="$root.$data.os.isSignedIn" channel={ channel } ref="form"/>
-		<div v-if="!$root.$data.os.isSignedIn">
-			<p>参加するには<a href={ _URL_ }>ログインまたは新規登録</a>してください</p>
-		</div>
-		<hr>
-		<footer>
-			<small><a href={ _URL_ }>Misskey</a> ver { _VERSION_ } (葵 aoi)</small>
-		</footer>
-	</main>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-
-			> main
-				> h1
-					font-size 1.5em
-					color #f00
-
-				> .share
-					> *
-						margin-right 4px
-
-				> .body
-					margin 8px 0 0 0
-
-				> mk-channel-form
-					max-width 500px
-
-	</style>
-	<script lang="typescript">
-		import Progress from '../../common/scripts/loading';
-		import ChannelStream from '../../common/scripts/streaming/channel-stream';
-
-		this.mixin('i');
-		this.mixin('api');
-
-		this.id = this.opts.id;
-		this.fetching = true;
-		this.notesFetching = true;
-		this.channel = null;
-		this.notes = null;
-		this.connection = new ChannelStream(this.id);
-		this.unreadCount = 0;
-
-		this.on('mount', () => {
-			document.documentElement.style.background = '#efefef';
-
-			Progress.start();
-
-			let fetched = false;
-
-			// チャンネル概要読み込み
-			this.$root.$data.os.api('channels/show', {
-				channelId: this.id
-			}).then(channel => {
-				if (fetched) {
-					Progress.done();
-				} else {
-					Progress.set(0.5);
-					fetched = true;
-				}
-
-				this.update({
-					fetching: false,
-					channel: channel
-				});
-
-				document.title = channel.title + ' | Misskey'
-			});
-
-			// 投稿読み込み
-			this.$root.$data.os.api('channels/notes', {
-				channelId: this.id
-			}).then(notes => {
-				if (fetched) {
-					Progress.done();
-				} else {
-					Progress.set(0.5);
-					fetched = true;
-				}
-
-				this.update({
-					notesFetching: false,
-					notes: notes
-				});
-			});
-
-			this.connection.on('note', this.onNote);
-			document.addEventListener('visibilitychange', this.onVisibilitychange, false);
-		});
-
-		this.on('unmount', () => {
-			this.connection.off('note', this.onNote);
-			this.connection.close();
-			document.removeEventListener('visibilitychange', this.onVisibilitychange);
-		});
-
-		this.onNote = note => {
-			this.notes.unshift(note);
-			this.update();
-
-			if (document.hidden && this.$root.$data.os.isSignedIn && note.userId !== this.$root.$data.os.i.id) {
-				this.unreadCount++;
-				document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`;
-			}
-		};
-
-		this.onVisibilitychange = () => {
-			if (!document.hidden) {
-				this.unreadCount = 0;
-				document.title = this.channel.title + ' | Misskey'
-			}
-		};
-
-		this.watch = () => {
-			this.$root.$data.os.api('channels/watch', {
-				channelId: this.id
-			}).then(() => {
-				this.channel.isWatching = true;
-				this.update();
-			}, e => {
-				alert('error');
-			});
-		};
-
-		this.unwatch = () => {
-			this.$root.$data.os.api('channels/unwatch', {
-				channelId: this.id
-			}).then(() => {
-				this.channel.isWatching = false;
-				this.update();
-			}, e => {
-				alert('error');
-			});
-		};
-	</script>
-</mk-channel>
-
-<mk-channel-note>
-	<header>
-		<a class="index" @click="reply">{ note.index }:</a>
-		<a class="name" href={ _URL_ + '/@' + acct }><b>{ getUserName(note.user) }</b></a>
-		<mk-time time={ note.createdAt }/>
-		<mk-time time={ note.createdAt } mode="detail"/>
-		<span>ID:<i>{ acct }</i></span>
-	</header>
-	<div>
-		<a v-if="note.reply">&gt;&gt;{ note.reply.index }</a>
-		{ note.text }
-		<div class="media" v-if="note.media">
-			<template each={ file in note.media }>
-				<a href={ file.url } target="_blank">
-					<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/>
-				</a>
-			</template>
-		</div>
-	</div>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-			margin 0
-			padding 0
-
-			> header
-				position -webkit-sticky
-				position sticky
-				z-index 1
-				top 0
-				background rgba(239, 239, 239, 0.9)
-
-				> .index
-					margin-right 0.25em
-					color #000
-
-				> .name
-					margin-right 0.5em
-					color #008000
-
-				> mk-time
-					margin-right 0.5em
-
-					&:first-of-type
-						display none
-
-				@media (max-width 600px)
-					> mk-time
-						&:first-of-type
-							display initial
-
-						&:last-of-type
-							display none
-
-			> div
-				padding 0 0 1em 2em
-
-				> .media
-					> a
-						display inline-block
-
-						> img
-							max-width 100%
-							vertical-align bottom
-
-	</style>
-	<script lang="typescript">
-		import getAcct from '../../../../acct/render';
-		import getUserName from '../../../../renderers/get-user-name';
-
-		this.note = this.opts.note;
-		this.form = this.opts.form;
-		this.acct = getAcct(this.note.user);
-		this.name = getUserName(this.note.user);
-
-		this.reply = () => {
-			this.form.update({
-				reply: this.note
-			});
-		};
-	</script>
-</mk-channel-note>
-
-<mk-channel-form>
-	<p v-if="reply"><b>&gt;&gt;{ reply.index }</b> ({ getUserName(reply.user) }): <a @click="clearReply">[x]</a></p>
-	<textarea ref="text" disabled={ wait } oninput={ update } onkeydown={ onkeydown } onpaste={ onpaste } placeholder="%i18n:ch.tags.mk-channel-form.textarea%"></textarea>
-	<div class="actions">
-		<button @click="selectFile">%fa:upload%%i18n:ch.tags.mk-channel-form.upload%</button>
-		<button @click="drive">%fa:cloud%%i18n:ch.tags.mk-channel-form.drive%</button>
-		<button :class="{ wait: wait }" ref="submit" disabled={ wait || (refs.text.value.length == 0) } @click="note">
-			<template v-if="!wait">%fa:paper-plane%</template>{ wait ? '%i18n:!ch.tags.mk-channel-form.posting%' : '%i18n:!ch.tags.mk-channel-form.note%' }<mk-ellipsis v-if="wait"/>
-		</button>
-	</div>
-	<mk-uploader ref="uploader"/>
-	<ol v-if="files">
-		<li each={ files }>{ name }</li>
-	</ol>
-	<input ref="file" type="file" accept="image/*" multiple="multiple" onchange={ changeFile }/>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-
-			> textarea
-				width 100%
-				max-width 100%
-				min-width 100%
-				min-height 5em
-
-			> .actions
-				display flex
-
-				> button
-					> [data-fa]
-						margin-right 0.25em
-
-					&:last-child
-						margin-left auto
-
-					&.wait
-						cursor wait
-
-			> input[type='file']
-				display none
-
-	</style>
-	<script lang="typescript">
-		import getUserName from '../../../../renderers/get-user-name';
-
-		this.mixin('api');
-
-		this.channel = this.opts.channel;
-		this.files = null;
-
-		this.on('mount', () => {
-			this.$refs.uploader.on('uploaded', file => {
-				this.update({
-					files: [file]
-				});
-			});
-		});
-
-		this.upload = file => {
-			this.$refs.uploader.upload(file);
-		};
-
-		this.clearReply = () => {
-			this.update({
-				reply: null
-			});
-		};
-
-		this.clear = () => {
-			this.clearReply();
-			this.update({
-				files: null
-			});
-			this.$refs.text.value = '';
-		};
-
-		this.note = () => {
-			this.update({
-				wait: true
-			});
-
-			const files = this.files && this.files.length > 0
-				? this.files.map(f => f.id)
-				: undefined;
-
-			this.$root.$data.os.api('notes/create', {
-				text: this.$refs.text.value == '' ? undefined : this.$refs.text.value,
-				mediaIds: files,
-				replyId: this.reply ? this.reply.id : undefined,
-				channelId: this.channel.id
-			}).then(data => {
-				this.clear();
-			}).catch(err => {
-				alert('失敗した');
-			}).then(() => {
-				this.update({
-					wait: false
-				});
-			});
-		};
-
-		this.changeFile = () => {
-			Array.from(this.$refs.file.files).forEach(this.upload);
-		};
-
-		this.selectFile = () => {
-			this.$refs.file.click();
-		};
-
-		this.drive = () => {
-			window['cb'] = files => {
-				this.update({
-					files: files
-				});
-			};
-
-			window.open(_URL_ + '/selectdrive?multiple=true',
-				'drive_window',
-				'height=500,width=800');
-		};
-
-		this.onkeydown = e => {
-			if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();
-		};
-
-		this.onpaste = e => {
-			Array.from(e.clipboardData.items).forEach(item => {
-				if (item.kind == 'file') {
-					this.upload(item.getAsFile());
-				}
-			});
-		};
-
-		this.getUserName = getUserName;
-	</script>
-</mk-channel-form>
-
-<mk-twitter-button>
-	<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-show-count="false">Tweet</a>
-	<script lang="typescript">
-		this.on('mount', () => {
-			const head = document.getElementsByTagName('head')[0];
-			const script = document.createElement('script');
-			script.setAttribute('src', 'https://platform.twitter.com/widgets.js');
-			script.setAttribute('async', 'async');
-			head.appendChild(script);
-		});
-	</script>
-</mk-twitter-button>
-
-<mk-line-button>
-	<div class="line-it-button" data-lang="ja" data-type="share-a" data-url={ _CH_URL_ } style="display: none;"></div>
-	<script lang="typescript">
-		this.on('mount', () => {
-			const head = document.getElementsByTagName('head')[0];
-			const script = document.createElement('script');
-			script.setAttribute('src', 'https://d.line-scdn.net/r/web/social-plugin/js/thirdparty/loader.min.js');
-			script.setAttribute('async', 'async');
-			head.appendChild(script);
-		});
-	</script>
-</mk-line-button>
diff --git a/src/client/app/ch/tags/header.tag b/src/client/app/ch/tags/header.tag
deleted file mode 100644
index 901123d63..000000000
--- a/src/client/app/ch/tags/header.tag
+++ /dev/null
@@ -1,20 +0,0 @@
-<mk-header>
-	<div>
-		<a href={ _CH_URL_ }>Index</a> | <a href={ _URL_ }>Misskey</a>
-	</div>
-	<div>
-		<a v-if="!$root.$data.os.isSignedIn" href={ _URL_ }>ログイン(新規登録)</a>
-		<a v-if="$root.$data.os.isSignedIn" href={ _URL_ + '/@' + I.username }>{ I.username }</a>
-	</div>
-	<style lang="stylus" scoped>
-		:scope
-			display flex
-
-			> div:last-child
-				margin-left auto
-
-	</style>
-	<script lang="typescript">
-		this.mixin('i');
-	</script>
-</mk-header>
diff --git a/src/client/app/ch/tags/index.tag b/src/client/app/ch/tags/index.tag
deleted file mode 100644
index 529b83b2c..000000000
--- a/src/client/app/ch/tags/index.tag
+++ /dev/null
@@ -1,37 +0,0 @@
-<mk-index>
-	<mk-header/>
-	<hr>
-	<button @click="n">%i18n:ch.tags.mk-index.new%</button>
-	<hr>
-	<ul v-if="channels">
-		<li each={ channels }><a href={ '/' + this.id }>{ this.title }</a></li>
-	</ul>
-	<style lang="stylus" scoped>
-		:scope
-			display block
-
-	</style>
-	<script lang="typescript">
-		this.mixin('api');
-
-		this.on('mount', () => {
-			this.$root.$data.os.api('channels', {
-				limit: 100
-			}).then(channels => {
-				this.update({
-					channels: channels
-				});
-			});
-		});
-
-		this.n = () => {
-			const title = window.prompt('%i18n:!ch.tags.mk-index.channel-title%');
-
-			this.$root.$data.os.api('channels/create', {
-				title: title
-			}).then(channel => {
-				location.href = '/' + channel.id;
-			});
-		};
-	</script>
-</mk-index>
diff --git a/src/client/app/ch/tags/index.ts b/src/client/app/ch/tags/index.ts
deleted file mode 100644
index 12ffdaeb8..000000000
--- a/src/client/app/ch/tags/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-require('./index.tag');
-require('./channel.tag');
-require('./header.tag');
diff --git a/src/client/app/common/scripts/streaming/channel.ts b/src/client/app/common/scripts/streaming/channel.ts
deleted file mode 100644
index be68ec099..000000000
--- a/src/client/app/common/scripts/streaming/channel.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import Stream from './stream';
-import MiOS from '../../../mios';
-
-/**
- * Channel stream connection
- */
-export default class Connection extends Stream {
-	constructor(os: MiOS, channelId) {
-		super(os, 'channel', {
-			channel: channelId
-		});
-	}
-}
diff --git a/src/client/app/common/views/widgets/access-log.vue b/src/client/app/common/views/widgets/access-log.vue
deleted file mode 100644
index 8652e3564..000000000
--- a/src/client/app/common/views/widgets/access-log.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-<template>
-<div class="mkw-access-log">
-	<mk-widget-container :show-header="props.design == 0">
-		<template slot="header">%fa:server%%i18n:@title%</template>
-
-		<div :class="$style.logs" ref="log">
-			<p v-for="req in requests">
-				<span :class="$style.ip" :style="`color:${ req.fg }; background:${ req.bg }`">{{ req.ip }}</span>
-				<b>{{ req.method }}</b>
-				<span>{{ req.path }}</span>
-			</p>
-		</div>
-	</mk-widget-container>
-</div>
-</template>
-
-<script lang="ts">
-import define from '../../../common/define-widget';
-import * as seedrandom from 'seedrandom';
-
-export default define({
-	name: 'broadcast',
-	props: () => ({
-		design: 0
-	})
-}).extend({
-	data() {
-		return {
-			requests: [],
-			connection: null,
-			connectionId: null
-		};
-	},
-	mounted() {
-		this.connection = (this as any).os.streams.requestsStream.getConnection();
-		this.connectionId = (this as any).os.streams.requestsStream.use();
-		this.connection.on('request', this.onRequest);
-	},
-	beforeDestroy() {
-		this.connection.off('request', this.onRequest);
-		(this as any).os.streams.requestsStream.dispose(this.connectionId);
-	},
-	methods: {
-		onRequest(request) {
-			const random = seedrandom(request.ip);
-			const r = Math.floor(random() * 255);
-			const g = Math.floor(random() * 255);
-			const b = Math.floor(random() * 255);
-			const luma = (0.2126 * r) + (0.7152 * g) + (0.0722 * b); // SMPTE C, Rec. 709 weightings
-			request.bg = `rgb(${r}, ${g}, ${b})`;
-			request.fg = luma >= 165 ? '#000' : '#fff';
-
-			this.requests.push(request);
-			if (this.requests.length > 30) this.requests.shift();
-
-			(this.$refs.log as any).scrollTop = (this.$refs.log as any).scrollHeight;
-		},
-		func() {
-			if (this.props.design == 1) {
-				this.props.design = 0;
-			} else {
-				this.props.design++;
-			}
-			this.save();
-		}
-	}
-});
-</script>
-
-<style lang="stylus" module>
-.logs
-	max-height 250px
-	overflow auto
-
-	> p
-		margin 0
-		padding 8px
-		font-size 0.8em
-		color #555
-
-		&:nth-child(odd)
-			background rgba(#000, 0.025)
-
-		> b
-			margin-right 4px
-
-.ip
-	margin-right 4px
-	padding 0 4px
-
-</style>
diff --git a/src/client/app/common/views/widgets/index.ts b/src/client/app/common/views/widgets/index.ts
index e41030e85..9107d90ce 100644
--- a/src/client/app/common/views/widgets/index.ts
+++ b/src/client/app/common/views/widgets/index.ts
@@ -1,6 +1,5 @@
 import Vue from 'vue';
 
-import wAccessLog from './access-log.vue';
 import wVersion from './version.vue';
 import wRss from './rss.vue';
 import wServer from './server.vue';
@@ -22,4 +21,3 @@ Vue.component('mkw-broadcast', wBroadcast);
 Vue.component('mkw-server', wServer);
 Vue.component('mkw-rss', wRss);
 Vue.component('mkw-version', wVersion);
-Vue.component('mkw-access-log', wAccessLog);
diff --git a/src/client/app/desktop/views/components/home.vue b/src/client/app/desktop/views/components/home.vue
index a3d7927cf..87dae5a80 100644
--- a/src/client/app/desktop/views/components/home.vue
+++ b/src/client/app/desktop/views/components/home.vue
@@ -21,8 +21,6 @@
 					<option value="polls">%i18n:@polls%</option>
 					<option value="post-form">%i18n:@post-form%</option>
 					<option value="messaging">%i18n:@messaging%</option>
-					<option value="channel">%i18n:@channel%</option>
-					<option value="access-log">%i18n:@access-log%</option>
 					<option value="server">%i18n:@server%</option>
 					<option value="donation">%i18n:@donation%</option>
 					<option value="nav">%i18n:@nav%</option>
diff --git a/src/client/app/desktop/views/components/mentions.vue b/src/client/app/desktop/views/components/mentions.vue
deleted file mode 100644
index 23fb7cccd..000000000
--- a/src/client/app/desktop/views/components/mentions.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<template>
-<div class="mk-mentions">
-	<header>
-		<span :data-active="mode == 'all'" @click="mode = 'all'">%i18n:@all%</span>
-		<span :data-active="mode == 'following'" @click="mode = 'following'">%i18n:@followed%</span>
-	</header>
-	<div class="fetching" v-if="fetching">
-		<mk-ellipsis-icon/>
-	</div>
-	<p class="empty" v-if="notes.length == 0 && !fetching">
-		%fa:R comments%
-		<span v-if="mode == 'all'">%i18n:@empty%</span>
-		<span v-if="mode == 'following'">%i18n:@empty-followed%</span>
-	</p>
-	<mk-notes :notes="notes" ref="timeline"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
-	data() {
-		return {
-			fetching: true,
-			moreFetching: false,
-			mode: 'all',
-			notes: []
-		};
-	},
-	watch: {
-		mode() {
-			this.fetch();
-		}
-	},
-	mounted() {
-		document.addEventListener('keydown', this.onDocumentKeydown);
-		window.addEventListener('scroll', this.onScroll);
-
-		this.fetch(() => this.$emit('loaded'));
-	},
-	beforeDestroy() {
-		document.removeEventListener('keydown', this.onDocumentKeydown);
-		window.removeEventListener('scroll', this.onScroll);
-	},
-	methods: {
-		onDocumentKeydown(e) {
-			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
-				if (e.which == 84) { // t
-					(this.$refs.timeline as any).focus();
-				}
-			}
-		},
-		onScroll() {
-			const current = window.scrollY + window.innerHeight;
-			if (current > document.body.offsetHeight - 8) this.more();
-		},
-		fetch(cb?) {
-			this.fetching = true;
-			this.notes =  [];
-			(this as any).api('notes/mentions', {
-				following: this.mode == 'following'
-			}).then(notes => {
-				this.notes = notes;
-				this.fetching = false;
-				if (cb) cb();
-			});
-		},
-		more() {
-			if (this.moreFetching || this.fetching || this.notes.length == 0) return;
-			this.moreFetching = true;
-			(this as any).api('notes/mentions', {
-				following: this.mode == 'following',
-				untilId: this.notes[this.notes.length - 1].id
-			}).then(notes => {
-				this.notes = this.notes.concat(notes);
-				this.moreFetching = false;
-			});
-		}
-	}
-});
-</script>
-
-<style lang="stylus" scoped>
-@import '~const.styl'
-
-.mk-mentions
-	background #fff
-	border solid 1px rgba(#000, 0.075)
-	border-radius 6px
-
-	> header
-		padding 8px 16px
-		border-bottom solid 1px #eee
-
-		> span
-			margin-right 16px
-			line-height 27px
-			font-size 18px
-			color #555
-
-			&:not([data-active])
-				color $theme-color
-				cursor pointer
-
-				&:hover
-					text-decoration underline
-
-	> .fetching
-		padding 64px 0
-
-	> .empty
-		display block
-		margin 0 auto
-		padding 32px
-		max-width 400px
-		text-align center
-		color #999
-
-		> [data-fa]
-			display block
-			margin-bottom 16px
-			font-size 3em
-			color #ccc
-
-</style>
diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue
index 8660a5f89..e23d3e5a5 100644
--- a/src/client/app/desktop/views/components/notes.note.vue
+++ b/src/client/app/desktop/views/components/notes.note.vue
@@ -33,9 +33,6 @@
 				</div>
 			</header>
 			<div class="body">
-				<p class="channel" v-if="p.channel">
-					<a :href="`${_CH_URL_}/${p.channel.id}`" target="_blank">{{ p.channel.title }}</a>:
-				</p>
 				<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 ? '隠す' : 'もっと見る' }}</span>
@@ -574,9 +571,6 @@ root(isDark)
 					.mk-url-preview
 						margin-top 8px
 
-					> .channel
-						margin 0
-
 					> .mk-poll
 						font-size 80%
 
diff --git a/src/client/app/desktop/views/widgets/channel.channel.form.vue b/src/client/app/desktop/views/widgets/channel.channel.form.vue
deleted file mode 100644
index f2744268b..000000000
--- a/src/client/app/desktop/views/widgets/channel.channel.form.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<template>
-<div class="form">
-	<input v-model="text" :disabled="wait" @keydown="onKeydown" placeholder="書いて">
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
-	data() {
-		return {
-			text: '',
-			wait: false
-		};
-	},
-	methods: {
-		onKeydown(e) {
-			if (e.which == 10 || e.which == 13) this.post();
-		},
-		post() {
-			this.wait = true;
-
-			let reply = null;
-
-			if (/^>>([0-9]+) /.test(this.text)) {
-				const index = this.text.match(/^>>([0-9]+) /)[1];
-				reply = (this.$parent as any).notes.find(p => p.index.toString() == index);
-				this.text = this.text.replace(/^>>([0-9]+) /, '');
-			}
-
-			(this as any).api('notes/create', {
-				text: this.text,
-				replyId: reply ? reply.id : undefined,
-				channelId: (this.$parent as any).channel.id
-			}).then(data => {
-				this.text = '';
-			}).catch(err => {
-				alert('失敗した');
-			}).then(() => {
-				this.wait = false;
-			});
-		}
-	}
-});
-</script>
-
-<style lang="stylus" scoped>
-.form
-	width 100%
-	height 38px
-	padding 4px
-	border-top solid 1px #ddd
-
-	> input
-		padding 0 8px
-		width 100%
-		height 100%
-		font-size 14px
-		color #55595c
-		border solid 1px #dadada
-		border-radius 4px
-
-		&:hover
-		&:focus
-			border-color #aeaeae
-
-</style>
diff --git a/src/client/app/desktop/views/widgets/channel.channel.note.vue b/src/client/app/desktop/views/widgets/channel.channel.note.vue
deleted file mode 100644
index 776791906..000000000
--- a/src/client/app/desktop/views/widgets/channel.channel.note.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<template>
-<div class="note">
-	<header>
-		<a class="index" @click="reply">{{ note.index }}:</a>
-		<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id"><b>{{ note.user | userName }}</b></router-link>
-		<span>ID:<i>{{ note.user | acct }}</i></span>
-	</header>
-	<div>
-		<a v-if="note.reply">&gt;&gt;{{ note.reply.index }}</a>
-		{{ note.text }}
-		<div class="media" v-if="note.media">
-			<a v-for="file in note.media" :href="file.url" target="_blank">
-				<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/>
-			</a>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
-	props: ['note'],
-	methods: {
-		reply() {
-			this.$emit('reply', this.note);
-		}
-	}
-});
-</script>
-
-<style lang="stylus" scoped>
-.note
-	margin 0
-	padding 0
-	color #444
-
-	> header
-		position -webkit-sticky
-		position sticky
-		z-index 1
-		top 0
-		padding 8px 4px 4px 16px
-		background rgba(255, 255, 255, 0.9)
-
-		> .index
-			margin-right 0.25em
-
-		> .name
-			margin-right 0.5em
-			color #008000
-
-	> div
-		padding 0 16px 16px 16px
-
-		> .media
-			> a
-				display inline-block
-
-				> img
-					max-width 100%
-					vertical-align bottom
-
-</style>
diff --git a/src/client/app/desktop/views/widgets/channel.channel.vue b/src/client/app/desktop/views/widgets/channel.channel.vue
deleted file mode 100644
index ea4d8f845..000000000
--- a/src/client/app/desktop/views/widgets/channel.channel.vue
+++ /dev/null
@@ -1,106 +0,0 @@
-<template>
-<div class="channel">
-	<p v-if="fetching">読み込み中<mk-ellipsis/></p>
-	<div v-if="!fetching" ref="notes" class="notes">
-		<p v-if="notes.length == 0">まだ投稿がありません</p>
-		<x-note class="note" v-for="note in notes.slice().reverse()" :note="note" :key="note.id" @reply="reply"/>
-	</div>
-	<x-form class="form" ref="form"/>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import ChannelStream from '../../../common/scripts/streaming/channel';
-import XForm from './channel.channel.form.vue';
-import XNote from './channel.channel.note.vue';
-
-export default Vue.extend({
-	components: {
-		XForm,
-		XNote
-	},
-	props: ['channel'],
-	data() {
-		return {
-			fetching: true,
-			notes: [],
-			connection: null
-		};
-	},
-	watch: {
-		channel() {
-			this.zap();
-		}
-	},
-	mounted() {
-		this.zap();
-	},
-	beforeDestroy() {
-		this.disconnect();
-	},
-	methods: {
-		zap() {
-			this.fetching = true;
-
-			(this as any).api('channels/notes', {
-				channelId: this.channel.id
-			}).then(notes => {
-				this.notes = notes;
-				this.fetching = false;
-
-				this.$nextTick(() => {
-					this.scrollToBottom();
-				});
-
-				this.disconnect();
-				this.connection = new ChannelStream((this as any).os, this.channel.id);
-				this.connection.on('note', this.onNote);
-			});
-		},
-		disconnect() {
-			if (this.connection) {
-				this.connection.off('note', this.onNote);
-				this.connection.close();
-			}
-		},
-		onNote(note) {
-			this.notes.unshift(note);
-			this.scrollToBottom();
-		},
-		scrollToBottom() {
-			(this.$refs.notes as any).scrollTop = (this.$refs.notes as any).scrollHeight;
-		},
-		reply(note) {
-			(this.$refs.form as any).text = `>>${ note.index } `;
-		}
-	}
-});
-</script>
-
-<style lang="stylus" scoped>
-.channel
-
-	> p
-		margin 0
-		padding 16px
-		text-align center
-		color #aaa
-
-	> .notes
-		height calc(100% - 38px)
-		overflow auto
-		font-size 0.9em
-
-		> .note
-			border-bottom solid 1px #eee
-
-			&:last-child
-				border-bottom none
-
-	> .form
-		position absolute
-		left 0
-		bottom 0
-
-</style>
diff --git a/src/client/app/desktop/views/widgets/channel.vue b/src/client/app/desktop/views/widgets/channel.vue
deleted file mode 100644
index d21aed40f..000000000
--- a/src/client/app/desktop/views/widgets/channel.vue
+++ /dev/null
@@ -1,108 +0,0 @@
-<template>
-<div class="mkw-channel">
-	<template v-if="!props.compact">
-		<p class="title">%fa:tv%{{ channel ? channel.title : '%i18n:!@title%' }}</p>
-		<button @click="settings" title="%i18n:@settings%">%fa:cog%</button>
-	</template>
-	<p class="get-started" v-if="props.channel == null">%i18n:@get-started%</p>
-	<x-channel class="channel" :channel="channel" v-if="channel != null"/>
-</div>
-</template>
-
-<script lang="ts">
-import define from '../../../common/define-widget';
-import XChannel from './channel.channel.vue';
-
-export default define({
-	name: 'server',
-	props: () => ({
-		channel: null,
-		compact: false
-	})
-}).extend({
-	components: {
-		XChannel
-	},
-	data() {
-		return {
-			fetching: true,
-			channel: null
-		};
-	},
-	mounted() {
-		if (this.props.channel) {
-				this.zap();
-			}
-	},
-	methods: {
-		func() {
-			this.props.compact = !this.props.compact;
-			this.save();
-		},
-		settings() {
-			const id = window.prompt('チャンネルID');
-			if (!id) return;
-			this.props.channel = id;
-			this.zap();
-		},
-		zap() {
-			this.fetching = true;
-
-			(this as any).api('channels/show', {
-				channelId: this.props.channel
-			}).then(channel => {
-				this.channel = channel;
-				this.fetching = false;
-			});
-		}
-	}
-});
-</script>
-
-<style lang="stylus" scoped>
-.mkw-channel
-	background #fff
-	border solid 1px rgba(#000, 0.075)
-	border-radius 6px
-	overflow hidden
-
-	> .title
-		z-index 2
-		margin 0
-		padding 0 16px
-		line-height 42px
-		font-size 0.9em
-		font-weight bold
-		color #888
-		box-shadow 0 1px rgba(#000, 0.07)
-
-		> [data-fa]
-			margin-right 4px
-
-	> button
-		position absolute
-		z-index 2
-		top 0
-		right 0
-		padding 0
-		width 42px
-		font-size 0.9em
-		line-height 42px
-		color #ccc
-
-		&:hover
-			color #aaa
-
-		&:active
-			color #999
-
-	> .get-started
-		margin 0
-		padding 16px
-		text-align center
-		color #aaa
-
-	> .channel
-		height 200px
-
-</style>
diff --git a/src/client/app/desktop/views/widgets/index.ts b/src/client/app/desktop/views/widgets/index.ts
index 77d771d6b..7c074080c 100644
--- a/src/client/app/desktop/views/widgets/index.ts
+++ b/src/client/app/desktop/views/widgets/index.ts
@@ -8,7 +8,6 @@ import wUsers from './users.vue';
 import wPolls from './polls.vue';
 import wPostForm from './post-form.vue';
 import wMessaging from './messaging.vue';
-import wChannel from './channel.vue';
 import wProfile from './profile.vue';
 
 Vue.component('mkw-notifications', wNotifications);
@@ -19,5 +18,4 @@ Vue.component('mkw-users', wUsers);
 Vue.component('mkw-polls', wPolls);
 Vue.component('mkw-post-form', wPostForm);
 Vue.component('mkw-messaging', wMessaging);
-Vue.component('mkw-channel', wChannel);
 Vue.component('mkw-profile', wProfile);
diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue
index 8181bab0a..83a957cfb 100644
--- a/src/client/app/mobile/views/components/note.vue
+++ b/src/client/app/mobile/views/components/note.vue
@@ -32,7 +32,6 @@
 				</div>
 			</header>
 			<div class="body">
-				<p class="channel" v-if="p.channel != null"><a target="_blank">{{ p.channel.title }}</a>:</p>
 				<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 ? '隠す' : 'もっと見る' }}</span>
@@ -470,9 +469,6 @@ root(isDark)
 					.mk-url-preview
 						margin-top 8px
 
-					> .channel
-						margin 0
-
 					> .tags
 						margin 4px 0 0 0
 
diff --git a/src/client/app/mobile/views/pages/widgets.vue b/src/client/app/mobile/views/pages/widgets.vue
index 509ce16ee..f0a087786 100644
--- a/src/client/app/mobile/views/pages/widgets.vue
+++ b/src/client/app/mobile/views/pages/widgets.vue
@@ -15,7 +15,6 @@
 					<option value="photo-stream">フォトストリーム</option>
 					<option value="slideshow">スライドショー</option>
 					<option value="version">バージョン</option>
-					<option value="access-log">アクセスログ</option>
 					<option value="server">サーバー情報</option>
 					<option value="donation">寄付のお願い</option>
 					<option value="nav">ナビゲーション</option>
diff --git a/src/models/channel-watching.ts b/src/models/channel-watching.ts
deleted file mode 100644
index 44ca06883..000000000
--- a/src/models/channel-watching.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as mongo from 'mongodb';
-import db from '../db/mongodb';
-
-const ChannelWatching = db.get<IChannelWatching>('channelWatching');
-export default ChannelWatching;
-
-export interface IChannelWatching {
-	_id: mongo.ObjectID;
-	createdAt: Date;
-	deletedAt: Date;
-	channelId: mongo.ObjectID;
-	userId: mongo.ObjectID;
-}
diff --git a/src/models/channel.ts b/src/models/channel.ts
deleted file mode 100644
index 67386ac07..000000000
--- a/src/models/channel.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as mongo from 'mongodb';
-import deepcopy = require('deepcopy');
-import { IUser } from './user';
-import Watching from './channel-watching';
-import db from '../db/mongodb';
-
-const Channel = db.get<IChannel>('channels');
-export default Channel;
-
-export type IChannel = {
-	_id: mongo.ObjectID;
-	createdAt: Date;
-	title: string;
-	userId: mongo.ObjectID;
-	index: number;
-	watchingCount: number;
-};
-
-/**
- * Pack a channel for API response
- *
- * @param channel target
- * @param me? serializee
- * @return response
- */
-export const pack = (
-	channel: string | mongo.ObjectID | IChannel,
-	me?: string | mongo.ObjectID | IUser
-) => new Promise<any>(async (resolve, reject) => {
-
-	let _channel: any;
-
-	// Populate the channel if 'channel' is ID
-	if (mongo.ObjectID.prototype.isPrototypeOf(channel)) {
-		_channel = await Channel.findOne({
-			_id: channel
-		});
-	} else if (typeof channel === 'string') {
-		_channel = await Channel.findOne({
-			_id: new mongo.ObjectID(channel)
-		});
-	} else {
-		_channel = deepcopy(channel);
-	}
-
-	// Rename _id to id
-	_channel.id = _channel._id;
-	delete _channel._id;
-
-	// Remove needless properties
-	delete _channel.userId;
-
-	// Me
-	const meId: mongo.ObjectID = me
-	? mongo.ObjectID.prototype.isPrototypeOf(me)
-		? me as mongo.ObjectID
-		: typeof me === 'string'
-			? new mongo.ObjectID(me)
-			: (me as IUser)._id
-	: null;
-
-	if (me) {
-		//#region Watchしているかどうか
-		const watch = await Watching.findOne({
-			userId: meId,
-			channelId: _channel.id,
-			deletedAt: { $exists: false }
-		});
-
-		_channel.isWatching = watch !== null;
-		//#endregion
-	}
-
-	resolve(_channel);
-});
diff --git a/src/models/note.ts b/src/models/note.ts
index f42bb2a49..507092336 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -4,7 +4,6 @@ import rap from '@prezzemolo/rap';
 import db from '../db/mongodb';
 import { IUser, pack as packUser } from './user';
 import { pack as packApp } from './app';
-import { pack as packChannel } from './channel';
 import PollVote, { deletePollVote } from './poll-vote';
 import Reaction, { deleteNoteReaction } from './note-reaction';
 import { pack as packFile } from './drive-file';
@@ -29,7 +28,6 @@ export function isValidCw(text: string): boolean {
 
 export type INote = {
 	_id: mongo.ObjectID;
-	channelId: mongo.ObjectID;
 	createdAt: Date;
 	deletedAt: Date;
 	mediaIds: mongo.ObjectID[];
@@ -258,11 +256,6 @@ export const pack = async (
 		_note.app = packApp(_note.appId);
 	}
 
-	// Populate channel
-	if (_note.channelId) {
-		_note.channel = packChannel(_note.channelId);
-	}
-
 	// Populate media
 	_note.media = hide ? [] : Promise.all(_note.mediaIds.map(fileId =>
 		packFile(fileId)
diff --git a/src/publishers/stream.ts b/src/publishers/stream.ts
index dcc03e39f..58a6ef49a 100644
--- a/src/publishers/stream.ts
+++ b/src/publishers/stream.ts
@@ -45,10 +45,6 @@ class MisskeyEvent {
 		this.publish(`othello-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishChannelStream(channelId: ID, type: string, value?: any): void {
-		this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value);
-	}
-
 	public publishLocalTimelineStream(note: any): void {
 		this.redisClient.publish('misskey:local-timeline', JSON.stringify(note));
 	}
@@ -79,4 +75,3 @@ export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
 export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
 export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
 export const publishOthelloGameStream = ev.publishOthelloGameStream.bind(ev);
-export const publishChannelStream = ev.publishChannelStream.bind(ev);
diff --git a/src/renderers/get-note-summary.ts b/src/renderers/get-note-summary.ts
index 0844c0b18..643e2d09b 100644
--- a/src/renderers/get-note-summary.ts
+++ b/src/renderers/get-note-summary.ts
@@ -9,9 +9,6 @@ const summarize = (note: any): string => {
 
 	let summary = '';
 
-	// チャンネル
-	summary += note.channel ? `${note.channel.title}:` : '';
-
 	// 本文
 	summary += note.text ? note.text : '';
 
diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts
index 734b8273f..7647c76d3 100644
--- a/src/server/api/endpoints.ts
+++ b/src/server/api/endpoints.ts
@@ -621,33 +621,7 @@ const endpoints: Endpoint[] = [
 		name: 'messaging/messages/create',
 		withCredential: true,
 		kind: 'messaging-write'
-	},
-	{
-		name: 'channels/create',
-		withCredential: true,
-		limit: {
-			duration: ms('1hour'),
-			max: 3,
-			minInterval: ms('10seconds')
-		}
-	},
-	{
-		name: 'channels/show'
-	},
-	{
-		name: 'channels/notes'
-	},
-	{
-		name: 'channels/watch',
-		withCredential: true
-	},
-	{
-		name: 'channels/unwatch',
-		withCredential: true
-	},
-	{
-		name: 'channels'
-	},
+	}
 ];
 
 export default endpoints;
diff --git a/src/server/api/endpoints/channels.ts b/src/server/api/endpoints/channels.ts
deleted file mode 100644
index ceef4b9cb..000000000
--- a/src/server/api/endpoints/channels.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Module dependencies
- */
-import $ from 'cafy'; import ID from '../../../cafy-id';
-import Channel, { pack } from '../../../models/channel';
-
-/**
- * Get all channels
- *
- * @param {any} params
- * @param {any} me
- * @return {Promise<any>}
- */
-module.exports = (params, me) => new Promise(async (res, rej) => {
-	// 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');
-
-	// Check if both of sinceId and untilId is specified
-	if (sinceId && untilId) {
-		return rej('cannot set sinceId and untilId');
-	}
-
-	// Construct query
-	const sort = {
-		_id: -1
-	};
-	const query = {} as any;
-	if (sinceId) {
-		sort._id = 1;
-		query._id = {
-			$gt: sinceId
-		};
-	} else if (untilId) {
-		query._id = {
-			$lt: untilId
-		};
-	}
-
-	// Issue query
-	const channels = await Channel
-		.find(query, {
-			limit: limit,
-			sort: sort
-		});
-
-	// Serialize
-	res(await Promise.all(channels.map(async channel =>
-		await pack(channel, me))));
-});
diff --git a/src/server/api/endpoints/channels/create.ts b/src/server/api/endpoints/channels/create.ts
deleted file mode 100644
index 0e3c9dc5a..000000000
--- a/src/server/api/endpoints/channels/create.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Module dependencies
- */
-import $ from 'cafy';
-import Channel from '../../../../models/channel';
-import Watching from '../../../../models/channel-watching';
-import { pack } from '../../../../models/channel';
-
-/**
- * Create a channel
- */
-module.exports = async (params, user) => new Promise(async (res, rej) => {
-	// Get 'title' parameter
-	const [title, titleErr] = $.str.range(1, 100).get(params.title);
-	if (titleErr) return rej('invalid title param');
-
-	// Create a channel
-	const channel = await Channel.insert({
-		createdAt: new Date(),
-		userId: user._id,
-		title: title,
-		index: 0,
-		watchingCount: 1
-	});
-
-	// Response
-	res(await pack(channel));
-
-	// Create Watching
-	await Watching.insert({
-		createdAt: new Date(),
-		userId: user._id,
-		channelId: channel._id
-	});
-});
diff --git a/src/server/api/endpoints/channels/notes.ts b/src/server/api/endpoints/channels/notes.ts
deleted file mode 100644
index 463152e74..000000000
--- a/src/server/api/endpoints/channels/notes.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Module dependencies
- */
-import $ from 'cafy'; import ID from '../../../../cafy-id';
-import { default as Channel, IChannel } from '../../../../models/channel';
-import Note, { pack } from '../../../../models/note';
-
-/**
- * Show a notes of a channel
- */
-module.exports = (params, user) => new Promise(async (res, rej) => {
-	// Get 'limit' parameter
-	const [limit = 1000, limitErr] = $.num.optional().range(1, 1000).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');
-
-	// Check if both of sinceId and untilId is specified
-	if (sinceId && untilId) {
-		return rej('cannot set sinceId and untilId');
-	}
-
-	// Get 'channelId' parameter
-	const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
-	if (channelIdErr) return rej('invalid channelId param');
-
-	// Fetch channel
-	const channel: IChannel = await Channel.findOne({
-		_id: channelId
-	});
-
-	if (channel === null) {
-		return rej('channel not found');
-	}
-
-	//#region Construct query
-	const sort = {
-		_id: -1
-	};
-
-	const query = {
-		channelId: channel._id
-	} as any;
-
-	if (sinceId) {
-		sort._id = 1;
-		query._id = {
-			$gt: sinceId
-		};
-	} else if (untilId) {
-		query._id = {
-			$lt: untilId
-		};
-	}
-	//#endregion Construct query
-
-	// Issue query
-	const notes = await Note
-		.find(query, {
-			limit: limit,
-			sort: sort
-		});
-
-	// Serialize
-	res(await Promise.all(notes.map(async (note) =>
-		await pack(note, user)
-	)));
-});
diff --git a/src/server/api/endpoints/channels/show.ts b/src/server/api/endpoints/channels/show.ts
deleted file mode 100644
index 1bba63d49..000000000
--- a/src/server/api/endpoints/channels/show.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Module dependencies
- */
-import $ from 'cafy'; import ID from '../../../../cafy-id';
-import Channel, { IChannel, pack } from '../../../../models/channel';
-
-/**
- * Show a channel
- */
-module.exports = (params, user) => new Promise(async (res, rej) => {
-	// Get 'channelId' parameter
-	const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
-	if (channelIdErr) return rej('invalid channelId param');
-
-	// Fetch channel
-	const channel: IChannel = await Channel.findOne({
-		_id: channelId
-	});
-
-	if (channel === null) {
-		return rej('channel not found');
-	}
-
-	// Serialize
-	res(await pack(channel, user));
-});
diff --git a/src/server/api/endpoints/channels/unwatch.ts b/src/server/api/endpoints/channels/unwatch.ts
deleted file mode 100644
index f7dddff46..000000000
--- a/src/server/api/endpoints/channels/unwatch.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Module dependencies
- */
-import $ from 'cafy'; import ID from '../../../../cafy-id';
-import Channel from '../../../../models/channel';
-import Watching from '../../../../models/channel-watching';
-
-/**
- * Unwatch a channel
- */
-module.exports = (params, user) => new Promise(async (res, rej) => {
-	// Get 'channelId' parameter
-	const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
-	if (channelIdErr) return rej('invalid channelId param');
-
-	//#region Fetch channel
-	const channel = await Channel.findOne({
-		_id: channelId
-	});
-
-	if (channel === null) {
-		return rej('channel not found');
-	}
-	//#endregion
-
-	//#region Check whether not watching
-	const exist = await Watching.findOne({
-		userId: user._id,
-		channelId: channel._id,
-		deletedAt: { $exists: false }
-	});
-
-	if (exist === null) {
-		return rej('already not watching');
-	}
-	//#endregion
-
-	// Delete watching
-	await Watching.update({
-		_id: exist._id
-	}, {
-		$set: {
-			deletedAt: new Date()
-		}
-	});
-
-	// Send response
-	res();
-
-	// Decrement watching count
-	Channel.update(channel._id, {
-		$inc: {
-			watchingCount: -1
-		}
-	});
-});
diff --git a/src/server/api/endpoints/channels/watch.ts b/src/server/api/endpoints/channels/watch.ts
deleted file mode 100644
index 34243ff68..000000000
--- a/src/server/api/endpoints/channels/watch.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Module dependencies
- */
-import $ from 'cafy'; import ID from '../../../../cafy-id';
-import Channel from '../../../../models/channel';
-import Watching from '../../../../models/channel-watching';
-
-/**
- * Watch a channel
- */
-module.exports = (params, user) => new Promise(async (res, rej) => {
-	// Get 'channelId' parameter
-	const [channelId, channelIdErr] = $.type(ID).get(params.channelId);
-	if (channelIdErr) return rej('invalid channelId param');
-
-	//#region Fetch channel
-	const channel = await Channel.findOne({
-		_id: channelId
-	});
-
-	if (channel === null) {
-		return rej('channel not found');
-	}
-	//#endregion
-
-	//#region Check whether already watching
-	const exist = await Watching.findOne({
-		userId: user._id,
-		channelId: channel._id,
-		deletedAt: { $exists: false }
-	});
-
-	if (exist !== null) {
-		return rej('already watching');
-	}
-	//#endregion
-
-	// Create Watching
-	await Watching.insert({
-		createdAt: new Date(),
-		userId: user._id,
-		channelId: channel._id
-	});
-
-	// Send response
-	res();
-
-	// Increment watching count
-	Channel.update(channel._id, {
-		$inc: {
-			watchingCount: 1
-		}
-	});
-});
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 429b6d370..182359f63 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -4,7 +4,6 @@
 import $ from 'cafy'; import ID from '../../../../cafy-id';
 import Note, { INote, isValidText, isValidCw, pack } from '../../../../models/note';
 import User, { ILocalUser } from '../../../../models/user';
-import Channel, { IChannel } from '../../../../models/channel';
 import DriveFile from '../../../../models/drive-file';
 import create from '../../../../services/note/create';
 import { IApp } from '../../../../models/app';
@@ -89,7 +88,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
 	if (renoteIdErr) return rej('invalid renoteId');
 
 	let renote: INote = null;
-	let isQuote = false;
 	if (renoteId !== undefined) {
 		// Fetch renote to note
 		renote = await Note.findOne({
@@ -101,8 +99,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
 		} else if (renote.renoteId && !renote.text && !renote.mediaIds) {
 			return rej('cannot renote to renote');
 		}
-
-		isQuote = text != null || files != null;
 	}
 
 	// Get 'replyId' parameter
@@ -126,47 +122,6 @@ module.exports = (params, user: ILocalUser, app: IApp) => new Promise(async (res
 		}
 	}
 
-	// Get 'channelId' parameter
-	const [channelId, channelIdErr] = $.type(ID).optional().get(params.channelId);
-	if (channelIdErr) return rej('invalid channelId');
-
-	let channel: IChannel = null;
-	if (channelId !== undefined) {
-		// Fetch channel
-		channel = await Channel.findOne({
-			_id: channelId
-		});
-
-		if (channel === null) {
-			return rej('channel not found');
-		}
-
-		// 返信対象の投稿がこのチャンネルじゃなかったらダメ
-		if (reply && !channelId.equals(reply.channelId)) {
-			return rej('チャンネル内部からチャンネル外部の投稿に返信することはできません');
-		}
-
-		// Renote対象の投稿がこのチャンネルじゃなかったらダメ
-		if (renote && !channelId.equals(renote.channelId)) {
-			return rej('チャンネル内部からチャンネル外部の投稿をRenoteすることはできません');
-		}
-
-		// 引用ではないRenoteはダメ
-		if (renote && !isQuote) {
-			return rej('チャンネル内部では引用ではないRenoteをすることはできません');
-		}
-	} else {
-		// 返信対象の投稿がチャンネルへの投稿だったらダメ
-		if (reply && reply.channelId != null) {
-			return rej('チャンネル外部からチャンネル内部の投稿に返信することはできません');
-		}
-
-		// Renote対象の投稿がチャンネルへの投稿だったらダメ
-		if (renote && renote.channelId != null) {
-			return rej('チャンネル外部からチャンネル内部の投稿をRenoteすることはできません');
-		}
-	}
-
 	// Get 'poll' parameter
 	const [poll, pollErr] = $.obj.optional().strict()
 		.have('choices', $.arr($.str)
diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts
index 78786d4a1..9f3255564 100644
--- a/src/server/api/endpoints/notes/timeline.ts
+++ b/src/server/api/endpoints/notes/timeline.ts
@@ -4,7 +4,6 @@
 import $ from 'cafy'; import ID from '../../../../cafy-id';
 import Note from '../../../../models/note';
 import Mute from '../../../../models/mute';
-import ChannelWatching from '../../../../models/channel-watching';
 import { getFriends } from '../../common/get-friends';
 import { pack } from '../../../../models/note';
 
@@ -45,18 +44,11 @@ module.exports = async (params, user, app) => {
 	const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $.bool.optional().get(params.includeRenotedMyNotes);
 	if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
 
-	const [followings, watchingChannelIds, mutedUserIds] = await Promise.all([
+	const [followings, mutedUserIds] = await Promise.all([
 		// フォローを取得
 		// Fetch following
 		getFriends(user._id),
 
-		// Watchしているチャンネルを取得
-		ChannelWatching.find({
-			userId: user._id,
-			// 削除されたドキュメントは除く
-			deletedAt: { $exists: false }
-		}).then(watches => watches.map(w => w.channelId)),
-
 		// ミュートしているユーザーを取得
 		Mute.find({
 			muterId: user._id
@@ -93,26 +85,9 @@ module.exports = async (params, user, app) => {
 
 	const query = {
 		$and: [{
-			$or: [{
-				$and: [{
-					// フォローしている人のタイムラインへの投稿
-					$or: followQuery
-				}, {
-					// 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る
-					$or: [{
-						channelId: {
-							$exists: false
-						}
-					}, {
-						channelId: null
-					}]
-				}]
-			}, {
-				// Watchしているチャンネルへの投稿
-				channelId: {
-					$in: watchingChannelIds
-				}
-			}],
+			// フォローしている人の投稿
+			$or: followQuery,
+
 			// mute
 			userId: {
 				$nin: mutedUserIds
diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts
deleted file mode 100644
index cb0427823..000000000
--- a/src/server/api/stream/channel.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as websocket from 'websocket';
-import * as redis from 'redis';
-import { ParsedUrlQuery } from 'querystring';
-
-export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void {
-	const q = request.resourceURL.query as ParsedUrlQuery;
-	const channel = q.channel;
-
-	// Subscribe channel stream
-	subscriber.subscribe(`misskey:channel-stream:${channel}`);
-	subscriber.on('message', (_, data) => {
-		connection.send(data);
-	});
-}
diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts
index e4884ed7c..6825b6336 100644
--- a/src/server/api/streaming.ts
+++ b/src/server/api/streaming.ts
@@ -14,7 +14,6 @@ import othelloGameStream from './stream/othello-game';
 import othelloStream from './stream/othello';
 import serverStream from './stream/server';
 import requestsStream from './stream/requests';
-import channelStream from './stream/channel';
 import { ParsedUrlQuery } from 'querystring';
 import authenticate from './authenticate';
 
@@ -48,11 +47,6 @@ module.exports = (server: http.Server) => {
 			subscriber.quit();
 		});
 
-		if (request.resourceURL.pathname === '/channel') {
-			channelStream(request, connection, subscriber);
-			return;
-		}
-
 		const q = request.resourceURL.query as ParsedUrlQuery;
 		const [user, app] = await authenticate(q.i as string);
 

From 670ccead0bd0685dee96f269858e6073874a00c6 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 07:51:34 +0900
Subject: [PATCH 06/10] Update

---
 locales/en.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locales/en.yml b/locales/en.yml
index 5cfe7e793..a7c4fcf81 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -1,7 +1,7 @@
 ---
 meta:
   lang: "English"
-  divider: ""
+  divider: " "
 common:
   misskey: "Share everything with others using Misskey."
   time:

From 48a9eee42581b9faa89886564154d16b26d48cb1 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 07:55:28 +0900
Subject: [PATCH 07/10] Updaye docs

---
 docs/translate.en.md | 10 +++-------
 docs/translate.fr.md | 10 +++-------
 docs/translate.ja.md |  4 ----
 3 files changed, 6 insertions(+), 18 deletions(-)

diff --git a/docs/translate.en.md b/docs/translate.en.md
index a80f472a3..cedb0bafc 100644
--- a/docs/translate.en.md
+++ b/docs/translate.en.md
@@ -1,12 +1,8 @@
-Misskey's Translation - English version
-============
-
-How to add a new language?
-----------------------
-Copy a language file to `/locales` and rename it after the language you wish to add.
+Misskey's Translation
+=====================
 
 If you find an untranslated part on Misskey:
--------------------------------
+--------------------------------------------
 
 1. Look for untranslated parts in the miskey's source code.
 	- For instance, if you find an untranslated part in: `src/client/app/mobile/views/pages/home.vue`.
diff --git a/docs/translate.fr.md b/docs/translate.fr.md
index 8350bcb36..463267daa 100644
--- a/docs/translate.fr.md
+++ b/docs/translate.fr.md
@@ -1,12 +1,8 @@
-Traduction de Misskey - Version Française
-============
-
-Comment ajouter une nouvelle langue ?
-----------------------
-Veuillez copier un fichier de langue dans /locales puis renommez-le du nom de la langue que vous voulez ajouter et modifier.
+Traduction de Misskey
+=====================
 
 Si vous trouvez un segment non-traduit sur Misskey :
--------------------------------
+----------------------------------------------------
 
 1. Veuillez chercher des parties non-traduites dans le code source de Misskey.
 	- Par exemple, supposons que vous trouviez un segment non-traduit dans : `src/client/app/mobile/views/pages/home.vue`.
diff --git a/docs/translate.ja.md b/docs/translate.ja.md
index 22172b12e..4804478d1 100644
--- a/docs/translate.ja.md
+++ b/docs/translate.ja.md
@@ -1,10 +1,6 @@
 Misskeyの翻訳
 ============
 
-新たな言語を追加するには
-----------------------
-/locales 内に既にある何らかの言語ファイルをコピーして、追加したい言語名にリネームして編集してください。
-
 Misskey内の未翻訳箇所を見つけたら
 -------------------------------
 

From 53eeb0e4466682b9168266f94539b09d2e6b6eab Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 08:42:04 +0900
Subject: [PATCH 08/10] Rename

---
 locales/{ja.yml => ja-JP.yml} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename locales/{ja.yml => ja-JP.yml} (100%)

diff --git a/locales/ja.yml b/locales/ja-JP.yml
similarity index 100%
rename from locales/ja.yml
rename to locales/ja-JP.yml

From a97296df4508f5fb6c7b9850dbd0e444cf53c3b9 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 08:48:22 +0900
Subject: [PATCH 09/10] Revert "Rename"

This reverts commit 53eeb0e4466682b9168266f94539b09d2e6b6eab.
---
 locales/{ja-JP.yml => ja.yml} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename locales/{ja-JP.yml => ja.yml} (100%)

diff --git a/locales/ja-JP.yml b/locales/ja.yml
similarity index 100%
rename from locales/ja-JP.yml
rename to locales/ja.yml

From 254fbbbc753fd1a0496f67204d3058f7cada107e Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 19 May 2018 20:31:13 +0900
Subject: [PATCH 10/10] [wip] better mobile setting

---
 locales/de.yml                                |   2 +-
 locales/en.yml                                |   2 +-
 locales/es.yml                                |   2 +-
 locales/fr.yml                                |   2 +-
 locales/it.yml                                |   2 +-
 locales/ja.yml                                |   2 +-
 locales/ko.yml                                |   2 +-
 locales/pl.yml                                |   2 +-
 locales/ru.yml                                |   2 +-
 locales/zh.yml                                |   2 +-
 package.json                                  |   1 +
 src/client/app/mobile/script.ts               |  13 ++
 src/client/app/mobile/style.styl              |   7 +-
 .../app/mobile/views/pages/settings.vue       |  65 +++++++--
 .../views/pages/settings/settings.profile.vue | 126 ++++++++++++++++++
 src/client/md.scss                            |  13 ++
 16 files changed, 220 insertions(+), 25 deletions(-)
 create mode 100644 src/client/app/mobile/views/pages/settings/settings.profile.vue
 create mode 100644 src/client/md.scss

diff --git a/locales/de.yml b/locales/de.yml
index 97d304e75..67b363c47 100644
--- a/locales/de.yml
+++ b/locales/de.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/locales/en.yml b/locales/en.yml
index 5b38f775d..93eda474e 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "Notifications"
   read-all: "Are you sure you want to mark all unread notifications as read?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "Profile settings"
   will-be-published: "These profile settings will be updated."
   name: "Name"
diff --git a/locales/es.yml b/locales/es.yml
index d091e7756..9ec80fa68 100644
--- a/locales/es.yml
+++ b/locales/es.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/locales/fr.yml b/locales/fr.yml
index 35ebd5f77..541ad2672 100644
--- a/locales/fr.yml
+++ b/locales/fr.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "Notifications"
   read-all: "Êtes vous sûr de vouloir marqués toutes les notifications non-lus en tant que lus?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "Réglages du profiles"
   will-be-published: "Ces profiles vont être publier"
   name: "Nom"
diff --git a/locales/it.yml b/locales/it.yml
index d091e7756..9ec80fa68 100644
--- a/locales/it.yml
+++ b/locales/it.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/locales/ja.yml b/locales/ja.yml
index 4563e9485..c3ee3e6c9 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -752,7 +752,7 @@ mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
 
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/locales/ko.yml b/locales/ko.yml
index d091e7756..9ec80fa68 100644
--- a/locales/ko.yml
+++ b/locales/ko.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/locales/pl.yml b/locales/pl.yml
index beafb9603..a58457e24 100644
--- a/locales/pl.yml
+++ b/locales/pl.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "Powiadomienia"
   read-all: "Czy na pewno chcesz oznaczyć wszystkie powiadomienia jako przeczytane?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "Ustawienia profilu"
   will-be-published: "Te ustawienia profilu zostaną zaktualizowane."
   name: "Nazwa"
diff --git a/locales/ru.yml b/locales/ru.yml
index d091e7756..9ec80fa68 100644
--- a/locales/ru.yml
+++ b/locales/ru.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/locales/zh.yml b/locales/zh.yml
index d091e7756..9ec80fa68 100644
--- a/locales/zh.yml
+++ b/locales/zh.yml
@@ -632,7 +632,7 @@ mobile/views/pages/note.vue:
 mobile/views/pages/notifications.vue:
   notifications: "通知"
   read-all: "すべての通知を既読にしますか?"
-mobile/views/pages/profile-setting.vue:
+mobile/views/pages/settings/settings.profile.vue:
   title: "プロフィール設定"
   will-be-published: "これらのプロフィールは公開されます。"
   name: "名前"
diff --git a/package.json b/package.json
index 9464d2d73..3fa261b8a 100644
--- a/package.json
+++ b/package.json
@@ -206,6 +206,7 @@
 		"vue-js-modal": "1.3.13",
 		"vue-json-tree-view": "2.1.4",
 		"vue-loader": "15.0.11",
+		"vue-material": "^1.0.0-beta-10.2",
 		"vue-router": "3.0.1",
 		"vue-template-compiler": "2.5.16",
 		"vuedraggable": "2.16.0",
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index 1405139be..427c177a1 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -2,11 +2,17 @@
  * Mobile Client
  */
 
+import Vue from 'vue';
 import VueRouter from 'vue-router';
 
+import { MdCard, MdButton, MdField, MdMenu, MdList, MdSwitch } from 'vue-material/dist/components';
+import 'vue-material/dist/vue-material.min.css';
+import 'vue-material/dist/theme/default.css';
+
 // Style
 import './style.styl';
 import '../../element.scss';
+import '../../md.scss';
 
 import init from '../init';
 
@@ -34,6 +40,13 @@ import MkSettings from './views/pages/settings.vue';
 import MkProfileSetting from './views/pages/profile-setting.vue';
 import MkOthello from './views/pages/othello.vue';
 
+Vue.use(MdCard);
+Vue.use(MdButton);
+Vue.use(MdField);
+Vue.use(MdMenu);
+Vue.use(MdList);
+Vue.use(MdSwitch);
+
 /**
  * init
  */
diff --git a/src/client/app/mobile/style.styl b/src/client/app/mobile/style.styl
index 847ae8eec..d1ab044ea 100644
--- a/src/client/app/mobile/style.styl
+++ b/src/client/app/mobile/style.styl
@@ -8,10 +8,13 @@
 
 html
 	height 100%
-	background #ececed
+	background #ececed !important
+
+	// for md
+	transition none !important
 
 	&[data-darkmode]
-		background #191B22
+		background #191B22 !important
 
 body
 	display flex
diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue
index 0e9c5ea96..b16860d62 100644
--- a/src/client/app/mobile/views/pages/settings.vue
+++ b/src/client/app/mobile/views/pages/settings.vue
@@ -1,18 +1,29 @@
 <template>
 <mk-ui>
 	<span slot="header">%fa:cog%%i18n:@settings%</span>
-	<div :class="$style.content">
+	<main>
 		<p v-html="'%i18n:!@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p>
-		<ul>
-			<li><router-link to="./settings/profile">%fa:user%%i18n:@profile%%fa:angle-right%</router-link></li>
-			<li><router-link to="./settings/twitter">%fa:B twitter%%i18n:@twitter%%fa:angle-right%</router-link></li>
-			<li><router-link to="./settings/signin-history">%fa:sign-in-alt%%i18n:@signin-history%%fa:angle-right%</router-link></li>
-		</ul>
-		<ul>
-			<li><a @click="signout">%fa:power-off%%i18n:@signout%</a></li>
-		</ul>
+		<div>
+			<x-profile/>
+
+			<md-card class="md-layout-item md-size-50 md-small-size-100">
+				<md-card-header>
+					<div class="md-title">%i18n:@design%</div>
+				</md-card-header>
+
+				<md-card-content>
+					<div>
+						<md-switch v-model="darkmode">%i18n:@dark-mode%</md-switch>
+					</div>
+
+					<div>
+						<md-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</md-switch>
+					</div>
+				</md-card-content>
+			</md-card>
+		</div>
 		<p><small>ver {{ version }} ({{ codename }})</small></p>
-	</div>
+	</main>
 </mk-ui>
 </template>
 
@@ -20,31 +31,59 @@
 import Vue from 'vue';
 import { version, codename } from '../../../config';
 
+import XProfile from './settings/settings.profile.vue';
+
 export default Vue.extend({
+	components: {
+		XProfile
+	},
+
 	data() {
 		return {
 			version,
-			codename
+			codename,
+			darkmode: localStorage.getItem('darkmode') == 'true'
 		};
 	},
+
 	computed: {
 		name(): string {
 			return Vue.filter('userName')((this as any).os.i);
 		}
 	},
+
+	watch: {
+		darkmode() {
+			(this as any)._updateDarkmode_(this.darkmode);
+		}
+	},
+
 	mounted() {
 		document.title = 'Misskey | %i18n:@settings%';
 	},
+
 	methods: {
 		signout() {
 			(this as any).os.signout();
+		},
+
+		onChangeCircleIcons(v) {
+			this.$store.dispatch('settings/set', {
+				key: 'circleIcons',
+				value: v
+			});
 		}
 	}
 });
 </script>
 
-<style lang="stylus" module>
-.content
+<style lang="stylus" scoped>
+main
+	padding 0 16px
+
+	> div
+		> *
+			margin-bottom 16px
 
 	> p
 		display block
diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue
new file mode 100644
index 000000000..6b5d07cce
--- /dev/null
+++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue
@@ -0,0 +1,126 @@
+<template>
+	<md-card class="md-layout-item md-size-50 md-small-size-100">
+		<md-card-header>
+			<div class="md-title">%i18n:@title%</div>
+		</md-card-header>
+
+		<md-card-content>
+			<md-field>
+				<label>%i18n:@name%</label>
+				<md-input v-model="name" :disabled="saving"/>
+			</md-field>
+
+			<md-field>
+				<label>%i18n:@location%</label>
+				<md-input v-model="location" :disabled="saving"/>
+			</md-field>
+
+			<md-field>
+				<label>%i18n:@description%</label>
+				<md-textarea v-model="description" :disabled="saving"/>
+			</md-field>
+
+			<md-field>
+				<label>%i18n:@birthday%</label>
+				<md-input type="date" v-model="birthday" :disabled="saving"/>
+			</md-field>
+
+			<div>
+				<div class="md-body-2">%i18n:@avatar%</div>
+				<md-menu md-direction="bottom-end" :md-close-on-select="true">
+					<md-button md-menu-trigger>%i18n:@set-avatar%</md-button>
+					<md-menu-content>
+						<md-menu-item @click="uploadAvatar">%i18n:@upload-avatar%</md-menu-item>
+						<md-menu-item @click="chooseAvatar">%i18n:@choose-avatar%</md-menu-item>
+					</md-menu-content>
+				</md-menu>
+			</div>
+
+			<div>
+				<div class="md-body-2">%i18n:@banner%</div>
+				<md-menu md-direction="bottom-end" :md-close-on-select="true">
+					<md-button md-menu-trigger>%i18n:@set-banner%</md-button>
+					<md-menu-content>
+						<md-menu-item @click="uploadAvatar">%i18n:@upload-banner%</md-menu-item>
+						<md-menu-item @click="chooseAvatar">%i18n:@choose-banner%</md-menu-item>
+					</md-menu-content>
+				</md-menu>
+			</div>
+		</md-card-content>
+
+		<md-card-actions>
+			<md-button class="md-primary" :disabled="saving" @click="save">%i18n:@save%</md-button>
+		</md-card-actions>
+	</md-card>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+	data() {
+		return {
+			name: null,
+			location: null,
+			description: null,
+			birthday: null,
+			saving: false
+		};
+	},
+	created() {
+		this.name = (this as any).os.i.name || '';
+		this.location = (this as any).os.i.profile.location;
+		this.description = (this as any).os.i.description;
+		this.birthday = (this as any).os.i.profile.birthday;
+	},
+	methods: {
+		chooseAvatar() {
+			(this as any).apis.chooseDriveFile({
+				multiple: false
+			}).then(file => {
+				this.avatarSaving = true;
+
+				(this as any).api('i/update', {
+					avatarId: file.id
+				}).then(() => {
+					this.avatarSaving = false;
+					alert('%i18n:!@avatar-saved%');
+				});
+			});
+		},
+		chooseBanner() {
+			(this as any).apis.chooseDriveFile({
+				multiple: false
+			}).then(file => {
+				this.bannerSaving = true;
+
+				(this as any).api('i/update', {
+					bannerId: file.id
+				}).then(() => {
+					this.bannerSaving = false;
+					alert('%i18n:!@banner-saved%');
+				});
+			});
+		},
+		uploadAvatar() {
+			// a
+		},
+		uploadBanner() {
+			// a
+		},
+		save() {
+			this.saving = true;
+
+			(this as any).api('i/update', {
+				name: this.name || null,
+				location: this.location || null,
+				description: this.description || null,
+				birthday: this.birthday || null
+			}).then(() => {
+				this.saving = false;
+				alert('%i18n:!@saved%');
+			});
+		}
+	}
+});
+</script>
diff --git a/src/client/md.scss b/src/client/md.scss
new file mode 100644
index 000000000..d850863ef
--- /dev/null
+++ b/src/client/md.scss
@@ -0,0 +1,13 @@
+/* SEE: https://vuematerial.io/themes/configuration */
+
+@import '../const.json';
+
+@import "~vue-material/dist/theme/engine";
+
+@include md-register-theme("default", (
+	primary: $themeColor,
+	accent: md-get-palette-color(red, A200)
+));
+
+@import "~vue-material/dist/components/MdButton/theme";
+@import "~vue-material/dist/components/MdField/theme";