diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 572c8ccdfe..66a9a83353 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -810,6 +810,10 @@ desktop/views/components/renote-form.vue:
 desktop/views/components/renote-form-window.vue:
   title: "この投稿をRenoteしますか?"
 
+desktop/views/pages/user-following-or-followers.vue:
+  following: "{user}のフォロー"
+  followers: "{user}のフォロワー"
+
 desktop/views/components/settings-window.vue:
   settings: "設定"
 
diff --git a/package.json b/package.json
index ea00e41178..0ff686c2fb 100644
--- a/package.json
+++ b/package.json
@@ -215,6 +215,7 @@
 		"vue-color": "2.7.0",
 		"vue-content-loading": "1.5.3",
 		"vue-cropperjs": "2.2.2",
+		"vue-i18n": "8.3.0",
 		"vue-js-modal": "1.3.26",
 		"vue-loader": "15.4.2",
 		"vue-router": "3.0.1",
diff --git a/src/client/app/common/views/components/ui/button.vue b/src/client/app/common/views/components/ui/button.vue
index 71496da5cd..132518da92 100644
--- a/src/client/app/common/views/components/ui/button.vue
+++ b/src/client/app/common/views/components/ui/button.vue
@@ -53,7 +53,7 @@ export default Vue.extend({
 	display block
 	width 100%
 	margin 0
-	padding 8px
+	padding 8px 10px
 	text-align center
 	font-weight normal
 	font-size 16px
diff --git a/src/client/app/config.ts b/src/client/app/config.ts
index 2abc3f7226..637d643d8d 100644
--- a/src/client/app/config.ts
+++ b/src/client/app/config.ts
@@ -1,5 +1,6 @@
 declare const _LANG_: string;
 declare const _LANGS_: string;
+declare const _LOCALE_: { [key: string]: any };
 declare const _THEME_COLOR_: string;
 declare const _COPYRIGHT_: string;
 declare const _VERSION_: string;
@@ -16,6 +17,7 @@ export const apiUrl = url + '/api';
 export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming';
 export const lang = _LANG_;
 export const langs = _LANGS_;
+export const locale = _LOCALE_;
 export const themeColor = _THEME_COLOR_;
 export const copyright = _COPYRIGHT_;
 export const version = _VERSION_;
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index eabb1fe163..ca9771e2fe 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -24,6 +24,7 @@ import MkIndex from './views/pages/index.vue';
 import MkHome from './views/pages/home.vue';
 import MkDeck from './views/pages/deck/deck.vue';
 import MkUser from './views/pages/user/user.vue';
+import MkUserFollowingOrFollowers from './views/pages/user-following-or-followers.vue';
 import MkFavorites from './views/pages/favorites.vue';
 import MkSelectDrive from './views/pages/selectdrive.vue';
 import MkDrive from './views/pages/drive.vue';
@@ -66,6 +67,8 @@ init(async (launch) => {
 			{ path: '/share', component: MkShare },
 			{ path: '/reversi/:game?', component: MkReversi },
 			{ path: '/@:user', name: 'user', component: MkUser },
+			{ path: '/@:user/following', name: 'userFollowing', component: MkUserFollowingOrFollowers },
+			{ path: '/@:user/followers', name: 'userFollowers', component: MkUserFollowingOrFollowers },
 			{ path: '/notes/:note', name: 'note', component: MkNote },
 			{ path: '/authorize-follow', component: MkFollow }
 		]
diff --git a/src/client/app/desktop/views/components/followers-window.vue b/src/client/app/desktop/views/components/followers-window.vue
deleted file mode 100644
index d5214adb2f..0000000000
--- a/src/client/app/desktop/views/components/followers-window.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<template>
-<mk-window width="400px" height="550px" @closed="destroyDom">
-	<span slot="header" :class="$style.header">
-		<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@followers%'.replace('{}', name) }}
-	</span>
-	<mk-followers :user="user"/>
-</mk-window>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
-	props: ['user'],
-	computed: {
-		name(): string {
-			return Vue.filter('userName')(this.user);
-		}
-	}
-});
-</script>
-
-<style lang="stylus" module>
-.header
-	> img
-		display inline-block
-		vertical-align bottom
-		height calc(100% - 10px)
-		margin 5px
-		border-radius 4px
-
-</style>
diff --git a/src/client/app/desktop/views/components/followers.vue b/src/client/app/desktop/views/components/followers.vue
deleted file mode 100644
index 1ef9f69771..0000000000
--- a/src/client/app/desktop/views/components/followers.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<template>
-<mk-users-list
-	:fetch="fetch"
-	:count="user.followersCount"
-	:you-know-count="user.followersYouKnowCount"
->
-	%i18n:@empty%
-</mk-users-list>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
-	props: ['user'],
-	methods: {
-		fetch(iknow, limit, cursor, cb) {
-			(this as any).api('users/followers', {
-				userId: this.user.id,
-				iknow: iknow,
-				limit: limit,
-				cursor: cursor ? cursor : undefined
-			}).then(cb);
-		}
-	}
-});
-</script>
diff --git a/src/client/app/desktop/views/components/following-window.vue b/src/client/app/desktop/views/components/following-window.vue
deleted file mode 100644
index aa9f2bde7b..0000000000
--- a/src/client/app/desktop/views/components/following-window.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<template>
-<mk-window width="400px" height="550px" @closed="destroyDom">
-	<span slot="header" :class="$style.header">
-		<img :src="user.avatarUrl" alt=""/>{{ '%i18n:@following%'.replace('{}', name) }}
-	</span>
-	<mk-following :user="user"/>
-</mk-window>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-
-export default Vue.extend({
-	props: ['user'],
-	computed: {
-		name(): string {
-			return Vue.filter('userName')(this.user);
-		}
-	}
-});
-</script>
-
-<style lang="stylus" module>
-.header
-	> img
-		display inline-block
-		vertical-align bottom
-		height calc(100% - 10px)
-		margin 5px
-		border-radius 4px
-
-</style>
diff --git a/src/client/app/desktop/views/components/following.vue b/src/client/app/desktop/views/components/following.vue
deleted file mode 100644
index d55ce1c0d4..0000000000
--- a/src/client/app/desktop/views/components/following.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<template>
-<mk-users-list
-	:fetch="fetch"
-	:count="user.followingCount"
-	:you-know-count="user.followingYouKnowCount"
->
-	%i18n:@empty%
-</mk-users-list>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
-	props: ['user'],
-	methods: {
-		fetch(iknow, limit, cursor, cb) {
-			(this as any).api('users/following', {
-				userId: this.user.id,
-				iknow: iknow,
-				limit: limit,
-				cursor: cursor ? cursor : undefined
-			}).then(cb);
-		}
-	}
-});
-</script>
diff --git a/src/client/app/desktop/views/components/index.ts b/src/client/app/desktop/views/components/index.ts
index ff4e845f62..2478f75ac4 100644
--- a/src/client/app/desktop/views/components/index.ts
+++ b/src/client/app/desktop/views/components/index.ts
@@ -22,9 +22,7 @@ import settings from './settings.vue';
 import calendar from './calendar.vue';
 import activity from './activity.vue';
 import friendsMaker from './friends-maker.vue';
-import followers from './followers.vue';
-import following from './following.vue';
-import usersList from './users-list.vue';
+import userCard from './user-card.vue';
 import userListTimeline from './user-list-timeline.vue';
 import widgetContainer from './widget-container.vue';
 
@@ -50,8 +48,6 @@ Vue.component('mk-settings', settings);
 Vue.component('mk-calendar', calendar);
 Vue.component('mk-activity', activity);
 Vue.component('mk-friends-maker', friendsMaker);
-Vue.component('mk-followers', followers);
-Vue.component('mk-following', following);
-Vue.component('mk-users-list', usersList);
+Vue.component('mk-user-card', userCard);
 Vue.component('mk-user-list-timeline', userListTimeline);
 Vue.component('mk-widget-container', widgetContainer);
diff --git a/src/client/app/desktop/views/components/users-list.item.vue b/src/client/app/desktop/views/components/user-card.vue
similarity index 77%
rename from src/client/app/desktop/views/components/users-list.item.vue
rename to src/client/app/desktop/views/components/user-card.vue
index 66a002c708..ccc0a49dc0 100644
--- a/src/client/app/desktop/views/components/users-list.item.vue
+++ b/src/client/app/desktop/views/components/user-card.vue
@@ -9,7 +9,6 @@
 		<div class="description">
 			<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
 		</div>
-		<p class="followed" v-if="user.isFollowed">%i18n:@followed%</p>
 	</div>
 </div>
 </template>
@@ -34,17 +33,18 @@ export default Vue.extend({
 
 <style lang="stylus" scoped>
 .zvdbznxvfixtmujpsigoccczftvpiwqh
-	$bg = #fff
+	$bg = var(--face)
 
-	margin 16px auto
-	max-width calc(100% - 32px)
-	font-size 16px
+	height 280px
+	overflow hidden
+	font-size 14px
 	text-align center
 	background $bg
 	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
+	color var(--faceText)
 
 	> .banner
-		height 100px
+		height 90px
 		background-color #f9f4f4
 		background-position center
 		background-size cover
@@ -63,13 +63,10 @@ export default Vue.extend({
 		right 16px
 
 	> .body
-		padding 4px 32px 32px 32px
-
-		@media (max-width 400px)
-			padding 4px 16px 16px 16px
+		padding 0px 24px
 
 		> .name
-			font-size 20px
+			font-size 120%
 			font-weight bold
 
 		> .username
@@ -77,15 +74,6 @@ export default Vue.extend({
 			opacity 0.7
 
 		> .description
-			margin 16px 0
-
-		> .followed
-			margin 0
-			padding 0
-			line-height 24px
-			font-size 0.8em
-			color #71afc7
-			background #eefaff
-			border-radius 4px
+			margin 8px 0 16px 0
 
 </style>
diff --git a/src/client/app/desktop/views/components/users-list.vue b/src/client/app/desktop/views/components/users-list.vue
deleted file mode 100644
index 05fe6c292e..0000000000
--- a/src/client/app/desktop/views/components/users-list.vue
+++ /dev/null
@@ -1,145 +0,0 @@
-<template>
-<div class="mk-users-list">
-	<nav>
-		<div>
-			<span :data-active="mode == 'all'" @click="mode = 'all'">%i18n:@all%<span>{{ count }}</span></span>
-			<span v-if="$store.getters.isSignedIn && youKnowCount" :data-active="mode == 'iknow'" @click="mode = 'iknow'">%i18n:@iknow%<span>{{ youKnowCount }}</span></span>
-		</div>
-	</nav>
-	<div class="users" v-if="!fetching && users.length != 0">
-		<div v-for="u in users" :key="u.id">
-			<x-item :user="u"/>
-		</div>
-	</div>
-	<button class="more" v-if="!fetching && next != null" @click="more" :disabled="moreFetching">
-		<span v-if="!moreFetching">%i18n:@load-more%</span>
-		<span v-if="moreFetching">%i18n:common.loading%<mk-ellipsis/></span>
-	</button>
-	<p class="no" v-if="!fetching && users.length == 0">
-		<slot></slot>
-	</p>
-	<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>%i18n:@fetching%<mk-ellipsis/></p>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-import XItem from './users-list.item.vue';
-
-export default Vue.extend({
-	components: {
-		XItem
-	},
-	props: ['fetch', 'count', 'youKnowCount'],
-	data() {
-		return {
-			limit: 20,
-			mode: 'all',
-			fetching: true,
-			moreFetching: false,
-			users: [],
-			next: null
-		};
-	},
-	mounted() {
-		this._fetch(() => {
-			this.$emit('loaded');
-		});
-	},
-	methods: {
-		_fetch(cb) {
-			this.fetching = true;
-			this.fetch(this.mode == 'iknow', this.limit, null, obj => {
-				this.users = obj.users;
-				this.next = obj.next;
-				this.fetching = false;
-				if (cb) cb();
-			});
-		},
-		more() {
-			this.moreFetching = true;
-			this.fetch(this.mode == 'iknow', this.limit, this.next, obj => {
-				this.moreFetching = false;
-				this.users = this.users.concat(obj.users);
-				this.next = obj.next;
-			});
-		}
-	}
-});
-</script>
-
-<style lang="stylus" scoped>
-
-
-.mk-users-list
-	height 100%
-	overflow auto
-	background #eee
-
-	> nav
-		z-index 10
-		position sticky
-		top 0
-		background #fff
-		box-shadow 0 1px 0 rgba(#000, 0.1)
-
-		> div
-			display flex
-			justify-content center
-			margin 0 auto
-			max-width 600px
-
-			> span
-				display block
-				flex 1 1
-				text-align center
-				line-height 52px
-				font-size 14px
-				color #657786
-				border-bottom solid 2px transparent
-				cursor pointer
-
-				*
-					pointer-events none
-
-				&[data-active]
-					font-weight bold
-					color var(--primary)
-					border-color var(--primary)
-					cursor default
-
-				> span
-					display inline-block
-					margin-left 4px
-					padding 2px 5px
-					font-size 12px
-					line-height 1
-					color #888
-					background #eee
-					border-radius 20px
-
-	> button
-		display block
-		width calc(100% - 32px)
-		margin 16px
-		padding 16px
-
-		&:hover
-			background rgba(#000, 0.1)
-
-	> .no
-		margin 0
-		padding 16px
-		text-align center
-		color #aaa
-
-	> .fetching
-		margin 0
-		padding 16px
-		text-align center
-		color #aaa
-
-		> [data-icon]
-			margin-right 4px
-
-</style>
diff --git a/src/client/app/desktop/views/pages/user-following-or-followers.vue b/src/client/app/desktop/views/pages/user-following-or-followers.vue
new file mode 100644
index 0000000000..db0de20b64
--- /dev/null
+++ b/src/client/app/desktop/views/pages/user-following-or-followers.vue
@@ -0,0 +1,126 @@
+<template>
+<mk-ui>
+	<div class="yyyocnobkvdlnyapyauyopbskldsnipz" v-if="!fetching">
+		<header>
+			<mk-avatar class="avatar" :user="user"/>
+			<i18n :path="isFollowing ? 'following' : 'followers'" tag="p">
+				<router-link :to="user | userPage" place="user">{{ user | userName }}</router-link>
+			</i18n>
+		</header>
+		<div class="users">
+			<mk-user-card v-for="user in users" :user="user" :key="user.id"/>
+		</div>
+		<div class="more" v-if="next">
+			<ui-button inline @click="fetchMore">%i18n:@load-more%</ui-button>
+		</div>
+	</div>
+</mk-ui>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import VueI18n from 'vue-i18n';
+import parseAcct from '../../../../../misc/acct/parse';
+import Progress from '../../../common/scripts/loading';
+import { lang, locale } from '../../../config';
+
+const limit = 16;
+
+const i18n = new VueI18n({
+	locale: lang,
+	messages: {
+		[lang]: locale['desktop/views/pages/user-following-or-followers.vue']
+	}
+});
+
+export default Vue.extend({
+	i18n,
+
+	data() {
+		return {
+			fetching: true,
+			user: null,
+			users: [],
+			next: undefined
+		};
+	},
+	computed: {
+		isFollowing(): boolean {
+			return this.$route.name == 'userFollowing';
+		},
+		endpoint(): string {
+			return this.isFollowing ? 'users/following' : 'users/followers';
+		}
+	},
+	watch: {
+		$route: 'fetch'
+	},
+	created() {
+		this.fetch();
+	},
+	methods: {
+		fetch() {
+			this.fetching = true;
+			Progress.start();
+			(this as any).api('users/show', parseAcct(this.$route.params.user)).then(user => {
+				this.user = user;
+				(this as any).api(this.endpoint, {
+					userId: this.user.id,
+					iknow: false,
+					limit: limit
+				}).then(x => {
+					this.users = x.users;
+					this.next = x.next;
+					this.fetching = false;
+					Progress.done();
+				});
+			});
+		},
+
+		fetchMore() {
+			(this as any).api(this.endpoint, {
+				userId: this.user.id,
+				iknow: false,
+				limit: limit,
+				cursor: this.next
+			}).then(x => {
+				this.users = this.users.concat(x.users);
+				this.next = x.next;
+			});
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.yyyocnobkvdlnyapyauyopbskldsnipz
+	width 100%
+	max-width 1280px
+	padding 16px
+	margin 0 auto
+
+	> header
+		display flex
+		align-items center
+		margin 0 0 16px 0
+		color var(--text)
+
+		> .avatar
+			width 64px
+			height 64px
+
+		> p
+			margin 0 16px
+			font-size 24px
+			font-weight bold
+
+	> .users
+		display grid
+		grid-template-columns 1fr 1fr 1fr 1fr
+		gap 16px
+
+	> .more
+		margin 32px 16px 16px 16px
+		text-align center
+
+</style>
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 4c30942828..6d7827d1ae 100644
--- a/src/client/app/desktop/views/pages/user/user.header.vue
+++ b/src/client/app/desktop/views/pages/user/user.header.vue
@@ -22,8 +22,8 @@
 		</div>
 		<div class="status">
 			<span class="notes-count"><b>{{ user.notesCount | number }}</b>%i18n:@posts%</span>
-			<span class="following clickable" @click="showFollowing"><b>{{ user.followingCount | number }}</b>%i18n:@following%</span>
-			<span class="followers clickable" @click="showFollowers"><b>{{ user.followersCount | number }}</b>%i18n:@followers%</span>
+			<router-link :to="user | userPage('following')" class="following clickable" @click="showFollowing"><b>{{ user.followingCount | number }}</b>%i18n:@following%</router-link>
+			<router-link :to="user | userPage('followers')" class="followers clickable" @click="showFollowers"><b>{{ user.followersCount | number }}</b>%i18n:@followers%</router-link>
 		</div>
 	</div>
 </div>
@@ -31,8 +31,6 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import MkFollowingWindow from '../../components/following-window.vue';
-import MkFollowersWindow from '../../components/followers-window.vue';
 import * as age from 's-age';
 
 export default Vue.extend({
@@ -84,19 +82,7 @@ export default Vue.extend({
 			(this as any).apis.updateBanner().then(i => {
 				this.user.bannerUrl = i.bannerUrl;
 			});
-		},
-
-		showFollowing() {
-			(this as any).os.new(MkFollowingWindow, {
-				user: this.user
-			});
-		},
-
-		showFollowers() {
-			(this as any).os.new(MkFollowersWindow, {
-				user: this.user
-			});
-		},
+		}
 	}
 });
 </script>
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index bc5a349987..3d1560633a 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -8,6 +8,7 @@ import VueRouter from 'vue-router';
 import VAnimateCss from 'v-animate-css';
 import VModal from 'vue-js-modal';
 import VueSweetalert2 from 'vue-sweetalert2';
+import VueI18n from 'vue-i18n';
 
 import VueHotkey from './common/hotkey';
 import App from './app.vue';
@@ -121,6 +122,7 @@ Vue.use(VAnimateCss);
 Vue.use(VModal);
 Vue.use(VueHotkey);
 Vue.use(VueSweetalert2);
+Vue.use(VueI18n);
 
 Vue.component('fa', FontAwesomeIcon);
 
diff --git a/webpack.config.ts b/webpack.config.ts
index 03ceeaa51c..37faec0069 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -66,6 +66,7 @@ const consts = {
 	_CODENAME_: codename,
 	_LANG_: '%lang%',
 	_LANGS_: Object.keys(locales).map(l => [l, locales[l].meta.lang]),
+	_LOCALE_: '%locale%',
 	_ENV_: process.env.NODE_ENV
 };
 
@@ -100,6 +101,7 @@ const plugins = [
 
 				src = src.replace(i18nReplacer.pattern, i18nReplacer.replacement);
 				src = src.replace('%lang%', lang);
+				src = src.replace('"%locale%"', JSON.stringify(locales[lang]));
 
 				fs.writeFileSync(`${__dirname}/built/client/assets/${file}.${version}.${lang}.js`, src, 'utf-8');
 			});