From 903e93ae01f3cf0dfbcb7bfb4b01167f5c03db25 Mon Sep 17 00:00:00 2001
From: Satsuki Yanagi <17376330+u1-liquid@users.noreply.github.com>
Date: Sun, 22 Mar 2020 10:51:40 +0900
Subject: [PATCH] i18n (#6171)

* i18n

Resolve #6155

* i18n for drive

* :v:

* Extract doc

Co-authored-by: syuilo <syuilotan@yahoo.co.jp>
---
 locales/ja-JP.yml                             | 57 +++++++++-------
 src/client/components/drive.file.vue          |  4 +-
 src/client/components/drive.folder.vue        | 16 ++---
 src/client/components/drive.vue               | 28 ++++----
 src/client/components/emoji-picker.vue        | 19 ++----
 src/client/components/signin.vue              |  4 +-
 src/client/pages/index.welcome.setup.vue      |  2 +-
 src/client/pages/messaging-room.form.vue      |  4 +-
 src/client/pages/messaging.vue                |  2 +-
 .../page-editor/els/page-editor.el.if.vue     |  2 +-
 .../els/page-editor.el.section.vue            |  2 +-
 .../page-editor/page-editor.script-block.vue  |  2 +-
 src/client/pages/page-editor/page-editor.vue  | 67 ++++++++-----------
 src/client/pages/pages.vue                    |  4 +-
 src/docs/pages.ja-JP.md                       | 10 +++
 15 files changed, 111 insertions(+), 112 deletions(-)
 create mode 100644 src/docs/pages.ja-JP.md

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 7918de552a..6eb90c9d02 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -88,7 +88,6 @@ add: "追加"
 reaction: "リアクション"
 reactionSettingDescription: "リアクションピッカーに表示するリアクションを設定します。"
 rememberNoteVisibility: "公開範囲を記憶する"
-renameFile: "ファイル名を変更"
 attachCancel: "添付取り消し"
 markAsSensitive: "閲覧注意にする"
 unmarkAsSensitive: "閲覧注意を解除する"
@@ -207,10 +206,13 @@ messaging: "チャット"
 upload: "アップロード"
 fromDrive: "ドライブから"
 fromUrl: "URLから"
+uploadFromUrl: "URLアップロード"
+uploadFromUrlDescription: "アップロードしたいファイルのURL"
+uploadFromUrlRequested: "アップロードをリクエストしました"
+uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。"
 explore: "みつける"
 games: "Misskey Games"
 messageRead: "既読"
-recentUsedEmojis: "最近使用した絵文字"
 noMoreHistory: "これより過去の履歴はありません"
 startMessaging: "チャットを開始"
 nUsersRead: "{n}人が読みました"
@@ -234,14 +236,22 @@ lightThemes: "明るいテーマ"
 darkThemes: "暗いテーマ"
 syncDeviceDarkMode: "デバイスのダークモードと同期する"
 drive: "ドライブ"
+fileName: "ファイル名"
 selectFile: "ファイルを選択"
 selectFiles: "ファイルを選択"
-renameFolder: "フォルダー名を変更"
+renameFile: "ファイル名を変更"
+folderName: "フォルダー名"
 createFolder: "フォルダーを作成"
+renameFolder: "フォルダー名を変更"
 deleteFolder: "フォルダーを削除"
 addFile: "ファイルを追加"
 emptyDrive: "ドライブは空です"
 emptyFolder: "フォルダーは空です"
+unableToDelete: "削除できません"
+inputNewFileName: "新しいファイル名を入力してください"
+inputNewFolderName: "新しいフォルダ名を入力してください"
+circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
+hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。"
 copyUrl: "URLをコピー"
 rename: "名前を変更"
 avatar: "アイコン"
@@ -395,13 +405,14 @@ strongPassword: "強いパスワード"
 passwordMatched: "一致しました"
 passwordNotMatched: "一致していません"
 signinWith: "{x}でログイン"
+signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
 tapSecurityKey: "セキュリティーキーにタッチ"
 or: "もしくは"
 uiLanguage: "UIの表示言語"
 groupInvited: "グループに招待されました"
 aboutX: "{x}について"
 useOsNativeEmojis: "OSネイティブの絵文字を使用"
-noGroups: "グループがありません"
+youHaveNoGroups: "グループがありません"
 joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
 noHistory: "履歴はありません"
 disableAnimatedMfm: "動きのあるMFMを無効にする"
@@ -453,6 +464,8 @@ none: "なし"
 volume: "音量"
 details: "詳細"
 chooseEmoji: "絵文字を選択"
+unableToProcess: "操作を完了できません"
+recentUsed: "最近使用"
 
 _sfx:
   note: "ノート"
@@ -679,45 +692,39 @@ _pages:
   newPage: "ページの作成"
   editPage: "ページの編集"
   readPage: "ソースを表示中"
-  page-created: "ページを作成しました"
-  page-updated: "ページを更新しました"
-  name-already-exists: "指定されたページURLは既に存在しています"
-  title-invalid-name: "不正なページURLです"
-  text-invalid-name: "空白でないか確認してください"
+  created: "ページを作成しました"
+  updated: "ページを更新しました"
+  deleted: "ページを削除しました"
+  nameAlreadyExists: "指定されたページURLは既に存在しています"
+  invalidNameTitle: "不正なページURLです"
+  invalidNameText: "空白でないか確認してください"
   editThisPage: "このページを編集"
   viewSource: "ソースを表示"
   viewPage: "ページを見る"
   like: "いいね"
   unlike: "いいね解除"
-  liked-pages: "いいねしたページ"
-  my-pages: "自分のページ"
+  my: "自分のページ"
+  liked: "いいねしたページ"
   inspector: "インスペクター"
   content: "ページブロック"
   variables: "変数"
-  variables-info: "変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。"
-  variables-info2: "変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b>や<b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b>や<b>C</b>を参照することはできません。"
-  variables-info3: "ユーザーからの入力を受け取るには、ページに「ユーザー入力」ブロックを設置し、「変数名」に入力を格納したい変数名を設定します(変数は自動で作成されます)。その変数を使ってユーザー入力に応じた動作を行えます。"
-  variables-info4: "関数を使うと、値の算出処理を再利用可能な形にまとめることができます。関数を作るには、「関数」タイプの変数を作成します。関数にはスロット(引数)を設定することができ、スロットの値は関数内で変数として利用可能です。また、AiScript標準で関数を引数に取る関数(高階関数と呼ばれます)も存在します。関数は予め定義しておくほかに、このような高階関数のスロットに即席でセットすることもできます。"
-  more-details: "詳しい説明"
   title: "タイトル"
   url: "ページURL"
   summary: "ページの要約"
   alignCenter: "中央寄せ"
-  hide-title-when-pinned: "ピン留めされているときにタイトルを非表示"
+  hideTitleWhenPinned: "ピン留めされているときにタイトルを非表示"
   font: "フォント"
   fontSerif: "セリフ"
   fontSansSerif: "サンセリフ"
-  set-eye-catching-image: "アイキャッチ画像を設定"
-  remove-eye-catching-image: "アイキャッチ画像を削除"
+  eyeCatchingImageSet: "アイキャッチ画像を設定"
+  eyeCatchingImageRemove: "アイキャッチ画像を削除"
   chooseBlock: "ブロックを追加"
   selectType: "種類を選択"
   enterVariableName: "変数名を決めてください"
-  the-variable-name-is-already-used: "その変数名は既に使われています"
-  content-blocks: "コンテンツ"
-  input-blocks: "入力"
-  special-blocks: "特殊"
-  post-from-post-form: "この内容を投稿"
-  posted-from-post-form: "投稿しました"
+  variableNameIsAlreadyUsed: "その変数名は既に使われています"
+  contentBlocks: "コンテンツ"
+  inputBlocks: "入力"
+  specialBlocks: "特殊"
   blocks:
     text: "テキスト"
     textarea: "テキストエリア"
diff --git a/src/client/components/drive.file.vue b/src/client/components/drive.file.vue
index 2ef3fd38e4..014e1a6ca5 100644
--- a/src/client/components/drive.file.vue
+++ b/src/client/components/drive.file.vue
@@ -139,9 +139,9 @@ export default Vue.extend({
 
 		rename() {
 			this.$root.dialog({
-				title: this.$t('contextmenu.rename-file'),
+				title: this.$t('renameFile'),
 				input: {
-					placeholder: this.$t('contextmenu.input-new-file-name'),
+					placeholder: this.$t('inputNewFileName'),
 					default: this.file.name,
 					allowEmpty: false
 				}
diff --git a/src/client/components/drive.folder.vue b/src/client/components/drive.folder.vue
index 658aced3e8..2e861e2480 100644
--- a/src/client/components/drive.folder.vue
+++ b/src/client/components/drive.folder.vue
@@ -137,14 +137,14 @@ export default Vue.extend({
 					switch (err) {
 						case 'detected-circular-definition':
 							this.$root.dialog({
-								title: this.$t('unable-to-process'),
-								text: this.$t('circular-reference-detected')
+								title: this.$t('unableToProcess'),
+								text: this.$t('circularReferenceFolder')
 							});
 							break;
 						default:
 							this.$root.dialog({
 								type: 'error',
-								text: this.$t('unhandled-error')
+								text: this.$t('error')
 							});
 					}
 				});
@@ -177,9 +177,9 @@ export default Vue.extend({
 
 		rename() {
 			this.$root.dialog({
-				title: this.$t('contextmenu.rename-folder'),
+				title: this.$t('renameFolder'),
 				input: {
-					placeholder: this.$t('contextmenu.input-new-folder-name'),
+					placeholder: this.$t('inputNewFolderName'),
 					default: this.folder.name
 				}
 			}).then(({ canceled, result: name }) => {
@@ -206,14 +206,14 @@ export default Vue.extend({
 					case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
 						this.$root.dialog({
 							type: 'error',
-							title: this.$t('unable-to-delete'),
-							text: this.$t('has-child-files-or-folders')
+							title: this.$t('unableToDelete'),
+							text: this.$t('hasChildFilesOrFolders')
 						});
 						break;
 					default:
 						this.$root.dialog({
 							type: 'error',
-							text: this.$t('unable-to-delete')
+							text: this.$t('unableToDelete')
 						});
 				}
 			});
diff --git a/src/client/components/drive.vue b/src/client/components/drive.vue
index bb35b156aa..fddac5b9aa 100644
--- a/src/client/components/drive.vue
+++ b/src/client/components/drive.vue
@@ -263,14 +263,14 @@ export default Vue.extend({
 					switch (err) {
 						case 'detected-circular-definition':
 							this.$root.dialog({
-								title: this.$t('unable-to-process'),
-								text: this.$t('circular-reference-detected')
+								title: this.$t('unableToProcess'),
+								text: this.$t('circularReferenceFolder')
 							});
 							break;
 						default:
 							this.$root.dialog({
 								type: 'error',
-								text: this.$t('unhandled-error')
+								text: this.$t('error')
 							});
 					}
 				});
@@ -284,9 +284,9 @@ export default Vue.extend({
 
 		urlUpload() {
 			this.$root.dialog({
-				title: this.$t('url-upload'),
+				title: this.$t('uploadFromUrl'),
 				input: {
-					placeholder: this.$t('url-of-file')
+					placeholder: this.$t('uploadFromUrlDescription')
 				}
 			}).then(({ canceled, result: url }) => {
 				if (canceled) return;
@@ -296,17 +296,17 @@ export default Vue.extend({
 				});
 
 				this.$root.dialog({
-					title: this.$t('url-upload-requested'),
-					text: this.$t('may-take-time')
+					title: this.$t('uploadFromUrlRequested'),
+					text: this.$t('uploadFromUrlMayTakeTime')
 				});
 			});
 		},
 
 		createFolder() {
 			this.$root.dialog({
-				title: this.$t('create-folder'),
+				title: this.$t('createFolder'),
 				input: {
-					placeholder: this.$t('folder-name')
+					placeholder: this.$t('folderName')
 				}
 			}).then(({ canceled, result: name }) => {
 				if (canceled) return;
@@ -321,9 +321,9 @@ export default Vue.extend({
 
 		renameFolder(folder) {
 			this.$root.dialog({
-				title: this.$t('contextmenu.rename-folder'),
+				title: this.$t('renameFolder'),
 				input: {
-					placeholder: this.$t('contextmenu.input-new-folder-name'),
+					placeholder: this.$t('inputNewFolderName'),
 					default: folder.name
 				}
 			}).then(({ canceled, result: name }) => {
@@ -349,14 +349,14 @@ export default Vue.extend({
 					case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
 						this.$root.dialog({
 							type: 'error',
-							title: this.$t('unable-to-delete'),
-							text: this.$t('has-child-files-or-folders')
+							title: this.$t('unableToDelete'),
+							text: this.$t('hasChildFilesOrFolders')
 						});
 						break;
 					default:
 						this.$root.dialog({
 							type: 'error',
-							text: this.$t('unable-to-delete')
+							text: this.$t('unableToDelete')
 						});
 					}
 			});
diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue
index a647b0ea04..e346023f29 100644
--- a/src/client/components/emoji-picker.vue
+++ b/src/client/components/emoji-picker.vue
@@ -2,12 +2,11 @@
 <x-popup :source="source" ref="popup" @closed="() => { $emit('closed'); destroyDom(); }">
 	<div class="omfetrab">
 		<header>
-			<button v-for="category in categories"
+			<button v-for="(category, i) in categories"
 				class="_button"
-				:title="category.text"
 				@click="go(category)"
 				:class="{ active: category.isActive }"
-				:key="category.text"
+				:key="i"
 			>
 				<fa :icon="category.icon" fixed-width/>
 			</button>
@@ -15,7 +14,7 @@
 
 		<div class="emojis">
 			<template v-if="categories[0].isActive">
-				<header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recentUsedEmojis') }}</header>
+				<header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recentUsed') }}</header>
 				<div class="list">
 					<button v-for="(emoji, i) in ($store.state.device.recentEmojis || [])"
 						class="_button"
@@ -27,9 +26,10 @@
 						<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
 					</button>
 				</div>
+
+				<header class="category"><fa :icon="faAsterisk" fixed-width/> {{ $t('customEmojis') }}</header>
 			</template>
 
-			<header class="category"><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
 			<template v-if="categories.find(x => x.isActive).name">
 				<div class="list">
 					<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
@@ -92,47 +92,38 @@ export default Vue.extend({
 			customEmojis: {},
 			faGlobe, faHistory,
 			categories: [{
-				text: this.$t('customEmoji'),
 				icon: faAsterisk,
 				isActive: true
 			}, {
 				name: 'people',
-				text: this.$t('people'),
 				icon: faLaugh,
 				isActive: false
 			}, {
 				name: 'animals_and_nature',
-				text: this.$t('animals-and-nature'),
 				icon: faLeaf,
 				isActive: false
 			}, {
 				name: 'food_and_drink',
-				text: this.$t('food-and-drink'),
 				icon: faUtensils,
 				isActive: false
 			}, {
 				name: 'activity',
-				text: this.$t('activity'),
 				icon: faFutbol,
 				isActive: false
 			}, {
 				name: 'travel_and_places',
-				text: this.$t('travel-and-places'),
 				icon: faCity,
 				isActive: false
 			}, {
 				name: 'objects',
-				text: this.$t('objects'),
 				icon: faDice,
 				isActive: false
 			}, {
 				name: 'symbols',
-				text: this.$t('symbols'),
 				icon: faHeart,
 				isActive: false
 			}, {
 				name: 'flags',
-				text: this.$t('flags'),
 				icon: faFlag,
 				isActive: false
 			}]
diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue
index 758bc59107..3d68289390 100755
--- a/src/client/components/signin.vue
+++ b/src/client/components/signin.vue
@@ -155,7 +155,7 @@ export default Vue.extend({
 				if (err === null) return;
 				this.$root.dialog({
 					type: 'error',
-					text: this.$t('login-failed')
+					text: this.$t('signinFailed')
 				});
 				this.signing = false;
 			});
@@ -176,7 +176,7 @@ export default Vue.extend({
 					}).catch(() => {
 						this.$root.dialog({
 							type: 'error',
-							text: this.$t('login-failed')
+							text: this.$t('signinFailed')
 						});
 						this.challengeData = null;
 						this.totpLogin = false;
diff --git a/src/client/pages/index.welcome.setup.vue b/src/client/pages/index.welcome.setup.vue
index a339ac0a28..6d08f5b5d4 100644
--- a/src/client/pages/index.welcome.setup.vue
+++ b/src/client/pages/index.welcome.setup.vue
@@ -61,7 +61,7 @@ export default Vue.extend({
 
 				this.$root.dialog({
 					type: 'error',
-					text: this.$t('some-error')
+					text: this.$t('error')
 				});
 			});
 		}
diff --git a/src/client/pages/messaging-room.form.vue b/src/client/pages/messaging-room.form.vue
index 14659fb1c4..72e2632772 100644
--- a/src/client/pages/messaging-room.form.vue
+++ b/src/client/pages/messaging-room.form.vue
@@ -113,7 +113,7 @@ export default Vue.extend({
 				if (items[0].kind == 'file') {
 					this.$root.dialog({
 						type: 'error',
-						text: this.$t('only-one-file-attached')
+						text: this.$t('onlyOneFileCanBeAttached')
 					});
 				}
 			}
@@ -138,7 +138,7 @@ export default Vue.extend({
 				e.preventDefault();
 				this.$root.dialog({
 					type: 'error',
-					text: this.$t('only-one-file-attached')
+					text: this.$t('onlyOneFileCanBeAttached')
 				});
 				return;
 			}
diff --git a/src/client/pages/messaging.vue b/src/client/pages/messaging.vue
index 2179115dea..47d761d895 100644
--- a/src/client/pages/messaging.vue
+++ b/src/client/pages/messaging.vue
@@ -145,7 +145,7 @@ export default Vue.extend({
 			if (groups1.length === 0 && groups2.length === 0) {
 				this.$root.dialog({
 					type: 'warning',
-					title: this.$t('noGroups'),
+					title: this.$t('youHaveNoGroups'),
 					text: this.$t('joinOrCreateGroup'),
 				});
 				return;
diff --git a/src/client/pages/page-editor/els/page-editor.el.if.vue b/src/client/pages/page-editor/els/page-editor.el.if.vue
index 3c545a7ddc..5e531a7ab5 100644
--- a/src/client/pages/page-editor/els/page-editor.el.if.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.if.vue
@@ -69,7 +69,7 @@ export default Vue.extend({
 		async add() {
 			const { canceled, result: type } = await this.$root.dialog({
 				type: null,
-				title: this.$t('choose-block'),
+				title: this.$t('_pages.chooseBlock'),
 				select: {
 					groupedItems: this.getPageBlockList()
 				},
diff --git a/src/client/pages/page-editor/els/page-editor.el.section.vue b/src/client/pages/page-editor/els/page-editor.el.section.vue
index d405ee1965..8de796e6d6 100644
--- a/src/client/pages/page-editor/els/page-editor.el.section.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.section.vue
@@ -80,7 +80,7 @@ export default Vue.extend({
 		async add() {
 			const { canceled, result: type } = await this.$root.dialog({
 				type: null,
-				title: this.$t('choose-block'),
+				title: this.$t('_pages.chooseBlock'),
 				select: {
 					groupedItems: this.getPageBlockList()
 				},
diff --git a/src/client/pages/page-editor/page-editor.script-block.vue b/src/client/pages/page-editor/page-editor.script-block.vue
index ae56803a39..f56e848c39 100644
--- a/src/client/pages/page-editor/page-editor.script-block.vue
+++ b/src/client/pages/page-editor/page-editor.script-block.vue
@@ -212,7 +212,7 @@ export default Vue.extend({
 		async changeType() {
 			const { canceled, result: type } = await this.$root.dialog({
 				type: null,
-				title: this.$t('select-type'),
+				title: this.$t('_pages.selectType'),
 				select: {
 					groupedItems: this.getScriptBlockList(this.getExpectedType ? this.getExpectedType() : null)
 				},
diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue
index 8b357584a5..6144b0bd9c 100644
--- a/src/client/pages/page-editor/page-editor.vue
+++ b/src/client/pages/page-editor/page-editor.vue
@@ -2,7 +2,7 @@
 <div>
 	<div class="gwbmwxkm _panel">
 		<header>
-			<div class="title"><fa :icon="faStickyNote"/> {{ readonly ? $t('readPage') : pageId ? $t('editPage') : $t('newPage') }}</div>
+			<div class="title"><fa :icon="faStickyNote"/> {{ readonly ? $t('_pages.readPage') : pageId ? $t('_pages.editPage') : $t('_pages.newPage') }}</div>
 			<div class="buttons">
 				<button class="_button" @click="del()" v-if="!readonly"><fa :icon="faTrashAlt"/></button>
 				<button class="_button" @click="() => showOptions = !showOptions"><fa :icon="faCog"/></button>
@@ -11,37 +11,37 @@
 		</header>
 
 		<section>
-			<router-link class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('view-page') }}</router-link>
+			<router-link class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</router-link>
 
 			<mk-input v-model="title">
-				<span>{{ $t('title') }}</span>
+				<span>{{ $t('_pages.title') }}</span>
 			</mk-input>
 
 			<template v-if="showOptions">
 				<mk-input v-model="summary">
-					<span>{{ $t('summary') }}</span>
+					<span>{{ $t('_pages.summary') }}</span>
 				</mk-input>
 
 				<mk-input v-model="name">
 					<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
-					<span>{{ $t('url') }}</span>
+					<span>{{ $t('_pages.url') }}</span>
 				</mk-input>
 
-				<mk-switch v-model="alignCenter">{{ $t('alignCenter') }}</mk-switch>
+				<mk-switch v-model="alignCenter">{{ $t('_pages.alignCenter') }}</mk-switch>
 
 				<mk-select v-model="font">
-					<template #label>{{ $t('font') }}</template>
-					<option value="serif">{{ $t('fontSerif') }}</option>
-					<option value="sans-serif">{{ $t('fontSansSerif') }}</option>
+					<template #label>{{ $t('_pages.font') }}</template>
+					<option value="serif">{{ $t('_pages.fontSerif') }}</option>
+					<option value="sans-serif">{{ $t('_pages.fontSansSerif') }}</option>
 				</mk-select>
 
-				<mk-switch v-model="hideTitleWhenPinned">{{ $t('hide-title-when-pinned') }}</mk-switch>
+				<mk-switch v-model="hideTitleWhenPinned">{{ $t('_pages.hideTitleWhenPinned') }}</mk-switch>
 
 				<div class="eyeCatch">
-					<mk-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</mk-button>
+					<mk-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('_pages.eyeCatchingImageSet') }}</mk-button>
 					<div v-else-if="eyeCatchingImage">
 						<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
-						<mk-button @click="removeEyeCatchingImage()" v-if="!readonly"><fa :icon="faTrashAlt"/> {{ $t('remove-eye-catching-image') }}</mk-button>
+						<mk-button @click="removeEyeCatchingImage()" v-if="!readonly"><fa :icon="faTrashAlt"/> {{ $t('_pages.eyeCatchingImageRemove') }}</mk-button>
 					</div>
 				</div>
 			</template>
@@ -53,7 +53,7 @@
 	</div>
 
 	<mk-container :body-togglable="true">
-		<template #header><fa :icon="faMagic"/> {{ $t('variables') }}</template>
+		<template #header><fa :icon="faMagic"/> {{ $t('_pages.variables') }}</template>
 		<div class="qmuvgica">
 			<x-draggable tag="div" class="variables" v-show="variables.length > 0" :list="variables" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
 				<x-variable v-for="variable in variables"
@@ -70,22 +70,14 @@
 			</x-draggable>
 
 			<mk-button @click="addVariable()" class="add" v-if="!readonly"><fa :icon="faPlus"/></mk-button>
-
-			<x-info><span v-html="$t('variables-info')"></span><a @click="() => moreDetails = true" style="display:block;">{{ $t('more-details') }}</a></x-info>
-
-			<template v-if="moreDetails">
-				<x-info><span v-html="$t('variables-info2')"></span></x-info>
-				<x-info><span v-html="$t('variables-info3')"></span></x-info>
-				<x-info><span v-html="$t('variables-info4')"></span></x-info>
-			</template>
 		</div>
 	</mk-container>
 
 	<mk-container :body-togglable="true" :expanded="false">
-		<template #header><fa :icon="faCode"/> {{ $t('inspector') }}</template>
+		<template #header><fa :icon="faCode"/> {{ $t('_pages.inspector') }}</template>
 		<div style="padding:0 32px 32px 32px;">
-			<mk-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('content') }}</mk-textarea>
-			<mk-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('variables') }}</mk-textarea>
+			<mk-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('_pages.content') }}</mk-textarea>
+			<mk-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('_pages.variables') }}</mk-textarea>
 		</div>
 	</mk-container>
 </div>
@@ -152,7 +144,6 @@ export default Vue.extend({
 			variables: [],
 			aiScript: null,
 			showOptions: false,
-			moreDetails: false,
 			url,
 			faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
 		};
@@ -243,14 +234,14 @@ export default Vue.extend({
 					if (err.info.param == 'name') {
 						this.$root.dialog({
 							type: 'error',
-							title: this.$t('title-invalid-name'),
-							text: this.$t('text-invalid-name')
+							title: this.$t('_pages.invalidNameTitle'),
+							text: this.$t('_pages.invalidNameText')
 						});
 					}
 				} else if (err.code == 'NAME_ALREADY_EXISTS') {
 					this.$root.dialog({
 						type: 'error',
-						text: this.$t('name-already-exists')
+						text: this.$t('_pages.nameAlreadyExists')
 					});
 				}
 			};
@@ -262,7 +253,7 @@ export default Vue.extend({
 					this.currentName = this.name.trim();
 					this.$root.dialog({
 						type: 'success',
-						text: this.$t('page-updated')
+						text: this.$t('_pages.updated')
 					});
 				}).catch(onError);
 			} else {
@@ -272,7 +263,7 @@ export default Vue.extend({
 					this.currentName = this.name.trim();
 					this.$root.dialog({
 						type: 'success',
-						text: this.$t('page-created')
+						text: this.$t('_pages.created')
 					});
 					this.$router.push(`/my/pages/edit/${this.pageId}`);
 				}).catch(onError);
@@ -282,7 +273,7 @@ export default Vue.extend({
 		del() {
 			this.$root.dialog({
 				type: 'warning',
-				text: this.$t('are-you-sure-delete'),
+				text: this.$t('removeAreYouSure', { x: this.title.trim() }),
 				showCancelButton: true
 			}).then(({ canceled }) => {
 				if (canceled) return;
@@ -291,7 +282,7 @@ export default Vue.extend({
 				}).then(() => {
 					this.$root.dialog({
 						type: 'success',
-						text: this.$t('page-deleted')
+						text: this.$t('_pages.deleted')
 					});
 					this.$router.push(`/my/pages`);
 				});
@@ -301,7 +292,7 @@ export default Vue.extend({
 		async add() {
 			const { canceled, result: type } = await this.$root.dialog({
 				type: null,
-				title: this.$t('chooseBlock'),
+				title: this.$t('_pages.chooseBlock'),
 				select: {
 					groupedItems: this.getPageBlockList()
 				},
@@ -315,7 +306,7 @@ export default Vue.extend({
 
 		async addVariable() {
 			let { canceled, result: name } = await this.$root.dialog({
-				title: this.$t('enterVariableName'),
+				title: this.$t('_pages.enterVariableName'),
 				input: {
 					type: 'text',
 				},
@@ -328,7 +319,7 @@ export default Vue.extend({
 			if (this.aiScript.isUsedName(name)) {
 				this.$root.dialog({
 					type: 'error',
-					text: this.$t('the-variable-name-is-already-used')
+					text: this.$t('_pages.variableNameIsAlreadyUsed')
 				});
 				return;
 			}
@@ -348,7 +339,7 @@ export default Vue.extend({
 
 		getPageBlockList() {
 			return [{
-				label: this.$t('content-blocks'),
+				label: this.$t('_pages.contentBlocks'),
 				items: [
 					{ value: 'section', text: this.$t('_pages.blocks.section') },
 					{ value: 'text', text: this.$t('_pages.blocks.text') },
@@ -356,7 +347,7 @@ export default Vue.extend({
 					{ value: 'textarea', text: this.$t('_pages.blocks.textarea') },
 				]
 			}, {
-				label: this.$t('input-blocks'),
+				label: this.$t('_pages.inputBlocks'),
 				items: [
 					{ value: 'button', text: this.$t('_pages.blocks.button') },
 					{ value: 'radioButton', text: this.$t('_pages.blocks.radioButton') },
@@ -367,7 +358,7 @@ export default Vue.extend({
 					{ value: 'counter', text: this.$t('_pages.blocks.counter') }
 				]
 			}, {
-				label: this.$t('special-blocks'),
+				label: this.$t('_pages.specialBlocks'),
 				items: [
 					{ value: 'if', text: this.$t('_pages.blocks.if') },
 					{ value: 'post', text: this.$t('_pages.blocks.post') }
diff --git a/src/client/pages/pages.vue b/src/client/pages/pages.vue
index bee7d30a61..d993d0196e 100644
--- a/src/client/pages/pages.vue
+++ b/src/client/pages/pages.vue
@@ -1,7 +1,7 @@
 <template>
 <div>
 	<mk-container :body-togglable="true">
-		<template #header><fa :icon="faEdit" fixed-width/>{{ $t('my-pages') }}</template>
+		<template #header><fa :icon="faEdit" fixed-width/>{{ $t('_pages.my') }}</template>
 		<div class="rknalgpo my">
 			<mk-button class="new" @click="create()"><fa :icon="faPlus"/></mk-button>
 			<mk-pagination :pagination="myPagesPagination" #default="{items}">
@@ -11,7 +11,7 @@
 	</mk-container>
 
 	<mk-container :body-togglable="true">
-		<template #header><fa :icon="faHeart" fixed-width/>{{ $t('liked-pages') }}</template>
+		<template #header><fa :icon="faHeart" fixed-width/>{{ $t('_pages.liked') }}</template>
 		<div class="rknalgpo">
 			<mk-pagination :pagination="likedPagesPagination" #default="{items}">
 				<mk-page-preview v-for="like in items" class="ckltabjg" :page="like.page" :key="like.page.id"/>
diff --git a/src/docs/pages.ja-JP.md b/src/docs/pages.ja-JP.md
new file mode 100644
index 0000000000..3804c5a5c2
--- /dev/null
+++ b/src/docs/pages.ja-JP.md
@@ -0,0 +1,10 @@
+# Pages
+
+## 変数
+変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。
+
+変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b>や<b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b>や<b>C</b>を参照することはできません。
+
+ユーザーからの入力を受け取るには、ページに「ユーザー入力」ブロックを設置し、「変数名」に入力を格納したい変数名を設定します(変数は自動で作成されます)。その変数を使ってユーザー入力に応じた動作を行えます。
+
+関数を使うと、値の算出処理を再利用可能な形にまとめることができます。関数を作るには、「関数」タイプの変数を作成します。関数にはスロット(引数)を設定することができ、スロットの値は関数内で変数として利用可能です。また、AiScript標準で関数を引数に取る関数(高階関数と呼ばれます)も存在します。関数は予め定義しておくほかに、このような高階関数のスロットに即席でセットすることもできます。