diff --git a/src/components/Dialog/DictionaryEditWordDialog.vue b/src/components/Dialog/DictionaryEditWordDialog.vue
new file mode 100644
index 0000000000..98df081902
--- /dev/null
+++ b/src/components/Dialog/DictionaryEditWordDialog.vue
@@ -0,0 +1,467 @@
+<template>
+  <div v-show="wordEditing" class="col-8 no-wrap text-no-wrap word-editor">
+    <div class="row q-pl-md q-mt-md">
+      <div class="text-h6">単語</div>
+      <QInput
+        ref="surfaceInput"
+        v-model="surface"
+        class="word-input"
+        dense
+        :disable="uiLocked"
+        @focus="clearSurfaceInputSelection()"
+        @blur="setSurface(surface)"
+        @keydown.enter="yomiFocus"
+      >
+        <ContextMenu
+          ref="surfaceContextMenu"
+          :header="surfaceContextMenuHeader"
+          :menudata="surfaceContextMenudata"
+          @beforeShow="startSurfaceContextMenuOperation()"
+          @beforeHide="endSurfaceContextMenuOperation()"
+        />
+      </QInput>
+    </div>
+    <div class="row q-pl-md q-pt-sm">
+      <div class="text-h6">読み</div>
+      <QInput
+        ref="yomiInput"
+        v-model="yomi"
+        class="word-input q-pb-none"
+        dense
+        :error="!isOnlyHiraOrKana"
+        :disable="uiLocked"
+        @focus="clearYomiInputSelection()"
+        @blur="setYomi(yomi)"
+        @keydown.enter="setYomiWhenEnter"
+      >
+        <template #error>
+          読みに使える文字はひらがなとカタカナのみです。
+        </template>
+        <ContextMenu
+          ref="yomiContextMenu"
+          :header="yomiContextMenuHeader"
+          :menudata="yomiContextMenudata"
+          @beforeShow="startYomiContextMenuOperation()"
+          @beforeHide="endYomiContextMenuOperation()"
+        />
+      </QInput>
+    </div>
+    <div class="row q-pl-md q-mt-lg text-h6">アクセント調整</div>
+    <div class="row q-pl-md desc-row">
+      語尾のアクセントを考慮するため、「が」が自動で挿入されます。
+    </div>
+    <div class="row q-px-md" style="height: 130px">
+      <div class="play-button">
+        <QBtn
+          v-if="!nowPlaying && !nowGenerating"
+          fab
+          color="primary"
+          textColor="display-on-primary"
+          icon="play_arrow"
+          @click="play"
+        />
+        <QBtn
+          v-else
+          fab
+          color="primary"
+          textColor="display-on-primary"
+          icon="stop"
+          :disable="nowGenerating"
+          @click="stop"
+        />
+      </div>
+      <div
+        ref="accentPhraseTable"
+        class="accent-phrase-table overflow-hidden-y"
+      >
+        <div v-if="accentPhrase" class="mora-table">
+          <AudioAccent
+            :accentPhrase
+            :accentPhraseIndex="0"
+            :uiLocked
+            :onChangeAccent="changeAccent"
+          />
+          <template
+            v-for="(mora, moraIndex) in accentPhrase.moras"
+            :key="moraIndex"
+          >
+            <div
+              class="text-cell"
+              :style="{
+                gridColumn: `${moraIndex * 2 + 1} / span 1`,
+              }"
+            >
+              {{ mora.text }}
+            </div>
+            <div
+              v-if="moraIndex < accentPhrase.moras.length - 1"
+              class="splitter-cell"
+              :style="{
+                gridColumn: `${moraIndex * 2 + 2} / span 1`,
+              }"
+            />
+          </template>
+        </div>
+      </div>
+    </div>
+    <div class="row q-pl-md q-pt-lg text-h6">単語優先度</div>
+    <div class="row q-pl-md desc-row">
+      単語を登録しても反映されない場合は優先度を高くしてください。
+    </div>
+    <div
+      class="row q-px-md"
+      :style="{
+        justifyContent: 'center',
+      }"
+    >
+      <QSlider
+        v-model="wordPriority"
+        snap
+        dense
+        color="primary"
+        markers
+        :min="0"
+        :max="10"
+        :step="1"
+        :markerLabels="wordPriorityLabels"
+        :style="{
+          width: '80%',
+        }"
+      />
+    </div>
+    <div class="row q-px-md save-delete-reset-buttons">
+      <QSpace />
+      <QBtn
+        v-show="!!selectedId"
+        outline
+        textColor="display"
+        class="text-no-wrap text-bold q-mr-sm"
+        :disable="uiLocked || !isWordChanged"
+        @click="resetWord(selectedId)"
+        >リセット</QBtn
+      >
+      <QBtn
+        outline
+        textColor="display"
+        class="text-no-wrap text-bold q-mr-sm"
+        :disable="uiLocked"
+        @click="discardOrNotDialog(cancel)"
+        >キャンセル</QBtn
+      >
+      <QBtn
+        outline
+        textColor="display"
+        class="text-no-wrap text-bold q-mr-sm"
+        :disable="uiLocked || !isWordChanged"
+        @click="saveWord"
+        >保存</QBtn
+      >
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { inject, ref } from "vue";
+import { QInput } from "quasar";
+import {
+  DictionaryManageDialogContext,
+  dictionaryManageDialogContextKey,
+} from "./DictionaryManageDialog.vue";
+import AudioAccent from "@/components/Talk/AudioAccent.vue";
+import ContextMenu from "@/components/Menu/ContextMenu/Container.vue";
+import { useRightClickContextMenu } from "@/composables/useRightClickContextMenu";
+import { useStore } from "@/store";
+import type { FetchAudioResult } from "@/store/type";
+
+const store = useStore();
+
+const context = inject<DictionaryManageDialogContext>(
+  dictionaryManageDialogContextKey,
+);
+if (context == undefined)
+  throw new Error(`dictionaryManageDialogContext == undefined`);
+const {
+  wordEditing,
+  surfaceInput,
+  selectedId,
+  uiLocked,
+  userDict,
+  isOnlyHiraOrKana,
+  accentPhrase,
+  voiceComputed,
+  surface,
+  yomi,
+  wordPriority,
+  isWordChanged,
+  setYomi,
+  createUILockAction,
+  loadingDictProcess,
+  computeRegisteredAccent,
+  discardOrNotDialog,
+  toInitialState,
+  toWordEditingState,
+  cancel,
+} = context;
+
+// 音声再生機構
+const nowGenerating = ref(false);
+const nowPlaying = ref(false);
+
+const play = async () => {
+  if (!accentPhrase.value) return;
+
+  nowGenerating.value = true;
+  const audioItem = await store.actions.GENERATE_AUDIO_ITEM({
+    text: yomi.value,
+    voice: voiceComputed.value,
+  });
+
+  if (audioItem.query == undefined)
+    throw new Error(`assert audioItem.query !== undefined`);
+
+  audioItem.query.accentPhrases = [accentPhrase.value];
+
+  let fetchAudioResult: FetchAudioResult;
+  try {
+    fetchAudioResult = await store.actions.FETCH_AUDIO_FROM_AUDIO_ITEM({
+      audioItem,
+    });
+  } catch (e) {
+    window.backend.logError(e);
+    nowGenerating.value = false;
+    void store.actions.SHOW_ALERT_DIALOG({
+      title: "生成に失敗しました",
+      message: "エンジンの再起動をお試しください。",
+    });
+    return;
+  }
+
+  const { blob } = fetchAudioResult;
+  nowGenerating.value = false;
+  nowPlaying.value = true;
+  await store.actions.PLAY_AUDIO_BLOB({ audioBlob: blob });
+  nowPlaying.value = false;
+};
+
+const stop = () => {
+  void store.actions.STOP_AUDIO();
+};
+
+// メニュー系
+const yomiInput = ref<QInput>();
+const wordPriorityLabels = {
+  0: "最低",
+  3: "低",
+  5: "標準",
+  7: "高",
+  10: "最高",
+};
+
+const yomiFocus = (event?: KeyboardEvent) => {
+  if (event && event.isComposing) return;
+  yomiInput.value?.focus();
+};
+
+const setYomiWhenEnter = (event?: KeyboardEvent) => {
+  if (event && event.isComposing) return;
+  void setYomi(yomi.value);
+};
+
+const convertHankakuToZenkaku = (text: string) => {
+  // " "などの目に見えない文字をまとめて全角スペース(0x3000)に置き換える
+  text = text.replace(/\p{Z}/gu, () => String.fromCharCode(0x3000));
+
+  // "!"から"~"までの範囲の文字(数字やアルファベット)を全角に置き換える
+  return text.replace(/[\u0021-\u007e]/g, (s) => {
+    return String.fromCharCode(s.charCodeAt(0) + 0xfee0);
+  });
+};
+
+const setSurface = (text: string) => {
+  // surfaceを全角化する
+  // 入力は半角でも問題ないが、登録時に全角に変換され、isWordChangedの判断がおかしくなることがあるので、
+  // 入力後に自動で変換するようにする
+  surface.value = convertHankakuToZenkaku(text);
+};
+
+const saveWord = async () => {
+  if (!accentPhrase.value) throw new Error(`accentPhrase === undefined`);
+  const accent = computeRegisteredAccent();
+  if (selectedId.value) {
+    try {
+      await store.actions.REWRITE_WORD({
+        wordUuid: selectedId.value,
+        surface: surface.value,
+        pronunciation: yomi.value,
+        accentType: accent,
+        priority: wordPriority.value,
+      });
+    } catch {
+      void store.actions.SHOW_ALERT_DIALOG({
+        title: "単語の更新に失敗しました",
+        message: "エンジンの再起動をお試しください。",
+      });
+      return;
+    }
+  } else {
+    try {
+      await createUILockAction(
+        store.actions.ADD_WORD({
+          surface: surface.value,
+          pronunciation: yomi.value,
+          accentType: accent,
+          priority: wordPriority.value,
+        }),
+      );
+    } catch {
+      void store.actions.SHOW_ALERT_DIALOG({
+        title: "単語の登録に失敗しました",
+        message: "エンジンの再起動をお試しください。",
+      });
+      return;
+    }
+  }
+  await loadingDictProcess();
+  toInitialState();
+};
+
+const resetWord = async (id: string) => {
+  const result = await store.actions.SHOW_WARNING_DIALOG({
+    title: "単語の変更をリセットしますか?",
+    message: "単語の変更は破棄されてリセットされます。",
+    actionName: "リセットする",
+  });
+  if (result === "OK") {
+    selectedId.value = id;
+    surface.value = userDict.value[id].surface;
+    void setYomi(userDict.value[id].yomi, true);
+    wordPriority.value = userDict.value[id].priority;
+    toWordEditingState();
+  }
+};
+
+// アクセント系
+const accentPhraseTable = ref<HTMLElement>();
+
+const changeAccent = async (_: number, accent: number) => {
+  const { engineId, styleId } = voiceComputed.value;
+
+  if (accentPhrase.value) {
+    accentPhrase.value.accent = accent;
+    accentPhrase.value = (
+      await createUILockAction(
+        store.actions.FETCH_MORA_DATA({
+          accentPhrases: [accentPhrase.value],
+          engineId,
+          styleId,
+        }),
+      )
+    )[0];
+  }
+};
+
+// コンテキストメニュー
+const surfaceContextMenu = ref<InstanceType<typeof ContextMenu>>();
+const yomiContextMenu = ref<InstanceType<typeof ContextMenu>>();
+
+const {
+  contextMenuHeader: surfaceContextMenuHeader,
+  contextMenudata: surfaceContextMenudata,
+  startContextMenuOperation: startSurfaceContextMenuOperation,
+  clearInputSelection: clearSurfaceInputSelection,
+  endContextMenuOperation: endSurfaceContextMenuOperation,
+} = useRightClickContextMenu(surfaceContextMenu, surfaceInput, surface);
+
+const {
+  contextMenuHeader: yomiContextMenuHeader,
+  contextMenudata: yomiContextMenudata,
+  startContextMenuOperation: startYomiContextMenuOperation,
+  clearInputSelection: clearYomiInputSelection,
+  endContextMenuOperation: endYomiContextMenuOperation,
+} = useRightClickContextMenu(yomiContextMenu, yomiInput, yomi);
+</script>
+
+<style lang="scss" scoped>
+@use "@/styles/colors" as colors;
+@use "@/styles/variables" as vars;
+
+.word-editor {
+  display: flex;
+  flex-flow: column;
+  height: calc(
+    100vh - #{vars.$menubar-height + vars.$toolbar-height +
+      vars.$window-border-width}
+  ) !important;
+  overflow: auto;
+}
+
+.word-input {
+  padding-left: 10px;
+  width: calc(66vw - 80px);
+
+  :deep(.q-field__control) {
+    height: 2rem;
+  }
+
+  :deep(.q-placeholder) {
+    padding: 0;
+    font-size: 20px;
+  }
+
+  :deep(.q-field__after) {
+    height: 2rem;
+  }
+}
+
+.desc-row {
+  color: rgba(colors.$display-rgb, 0.5);
+  font-size: 12px;
+}
+
+.play-button {
+  margin: auto 0;
+  padding-right: 16px;
+}
+
+.accent-phrase-table {
+  flex-grow: 1;
+  align-self: stretch;
+
+  display: flex;
+  height: 130px;
+  overflow-x: scroll;
+  width: calc(66vw - 140px);
+
+  .mora-table {
+    display: inline-grid;
+    align-self: stretch;
+    grid-template-rows: 1fr 60px 30px;
+
+    .text-cell {
+      padding: 0;
+      min-width: 20px;
+      max-width: 20px;
+      grid-row-start: 3;
+      text-align: center;
+      white-space: nowrap;
+      color: colors.$display;
+      position: relative;
+    }
+
+    .splitter-cell {
+      min-width: 20px;
+      max-width: 20px;
+      grid-row: 3 / span 1;
+      z-index: vars.$detail-view-splitter-cell-z-index;
+    }
+  }
+}
+
+.save-delete-reset-buttons {
+  padding: 20px;
+
+  display: flex;
+  flex: 1;
+  align-items: flex-end;
+}
+</style>
diff --git a/src/components/Dialog/DictionaryManageDialog.vue b/src/components/Dialog/DictionaryManageDialog.vue
index 50564d6c19..0bea44fccf 100644
--- a/src/components/Dialog/DictionaryManageDialog.vue
+++ b/src/components/Dialog/DictionaryManageDialog.vue
@@ -116,184 +116,53 @@
             </QList>
           </div>
 
-          <!-- 右側のpane -->
-          <div
-            v-show="wordEditing"
-            class="col-8 no-wrap text-no-wrap word-editor"
-          >
-            <div class="row q-pl-md q-mt-md">
-              <div class="text-h6">単語</div>
-              <QInput
-                ref="surfaceInput"
-                v-model="surface"
-                class="word-input"
-                dense
-                :disable="uiLocked"
-                @focus="clearSurfaceInputSelection()"
-                @blur="setSurface(surface)"
-                @keydown.enter="yomiFocus"
-              >
-                <ContextMenu
-                  ref="surfaceContextMenu"
-                  :header="surfaceContextMenuHeader"
-                  :menudata="surfaceContextMenudata"
-                  @beforeShow="startSurfaceContextMenuOperation()"
-                  @beforeHide="endSurfaceContextMenuOperation()"
-                />
-              </QInput>
-            </div>
-            <div class="row q-pl-md q-pt-sm">
-              <div class="text-h6">読み</div>
-              <QInput
-                ref="yomiInput"
-                v-model="yomi"
-                class="word-input q-pb-none"
-                dense
-                :error="!isOnlyHiraOrKana"
-                :disable="uiLocked"
-                @focus="clearYomiInputSelection()"
-                @blur="setYomi(yomi)"
-                @keydown.enter="setYomiWhenEnter"
-              >
-                <template #error>
-                  読みに使える文字はひらがなとカタカナのみです。
-                </template>
-                <ContextMenu
-                  ref="yomiContextMenu"
-                  :header="yomiContextMenuHeader"
-                  :menudata="yomiContextMenudata"
-                  @beforeShow="startYomiContextMenuOperation()"
-                  @beforeHide="endYomiContextMenuOperation()"
-                />
-              </QInput>
-            </div>
-            <div class="row q-pl-md q-mt-lg text-h6">アクセント調整</div>
-            <div class="row q-pl-md desc-row">
-              語尾のアクセントを考慮するため、「が」が自動で挿入されます。
-            </div>
-            <div class="row q-px-md" style="height: 130px">
-              <div class="play-button">
-                <QBtn
-                  v-if="!nowPlaying && !nowGenerating"
-                  fab
-                  color="primary"
-                  textColor="display-on-primary"
-                  icon="play_arrow"
-                  @click="play"
-                />
-                <QBtn
-                  v-else
-                  fab
-                  color="primary"
-                  textColor="display-on-primary"
-                  icon="stop"
-                  :disable="nowGenerating"
-                  @click="stop"
-                />
-              </div>
-              <div
-                ref="accentPhraseTable"
-                class="accent-phrase-table overflow-hidden-y"
-              >
-                <div v-if="accentPhrase" class="mora-table">
-                  <AudioAccent
-                    :accentPhrase
-                    :accentPhraseIndex="0"
-                    :uiLocked
-                    :onChangeAccent="changeAccent"
-                  />
-                  <template
-                    v-for="(mora, moraIndex) in accentPhrase.moras"
-                    :key="moraIndex"
-                  >
-                    <div
-                      class="text-cell"
-                      :style="{
-                        gridColumn: `${moraIndex * 2 + 1} / span 1`,
-                      }"
-                    >
-                      {{ mora.text }}
-                    </div>
-                    <div
-                      v-if="moraIndex < accentPhrase.moras.length - 1"
-                      class="splitter-cell"
-                      :style="{
-                        gridColumn: `${moraIndex * 2 + 2} / span 1`,
-                      }"
-                    />
-                  </template>
-                </div>
-              </div>
-            </div>
-            <div class="row q-pl-md q-pt-lg text-h6">単語優先度</div>
-            <div class="row q-pl-md desc-row">
-              単語を登録しても反映されない場合は優先度を高くしてください。
-            </div>
-            <div
-              class="row q-px-md"
-              :style="{
-                justifyContent: 'center',
-              }"
-            >
-              <QSlider
-                v-model="wordPriority"
-                snap
-                dense
-                color="primary"
-                markers
-                :min="0"
-                :max="10"
-                :step="1"
-                :markerLabels="wordPriorityLabels"
-                :style="{
-                  width: '80%',
-                }"
-              />
-            </div>
-            <div class="row q-px-md save-delete-reset-buttons">
-              <QSpace />
-              <QBtn
-                v-show="!!selectedId"
-                outline
-                textColor="display"
-                class="text-no-wrap text-bold q-mr-sm"
-                :disable="uiLocked || !isWordChanged"
-                @click="resetWord(selectedId)"
-                >リセット</QBtn
-              >
-              <QBtn
-                outline
-                textColor="display"
-                class="text-no-wrap text-bold q-mr-sm"
-                :disable="uiLocked"
-                @click="discardOrNotDialog(cancel)"
-                >キャンセル</QBtn
-              >
-              <QBtn
-                outline
-                textColor="display"
-                class="text-no-wrap text-bold q-mr-sm"
-                :disable="uiLocked || !isWordChanged"
-                @click="saveWord"
-                >保存</QBtn
-              >
-            </div>
-          </div>
+          <DictionaryEditWordDialog />
         </QPage>
       </QPageContainer>
     </QLayout>
   </QDialog>
 </template>
 
+<script lang="ts">
+import { Ref, ComputedRef } from "vue";
+
+export const dictionaryManageDialogContextKey = "dictionaryManageDialogContext";
+
+export interface DictionaryManageDialogContext {
+  wordEditing: Ref<boolean>;
+  surfaceInput: Ref<QInput | undefined>;
+  selectedId: Ref<string>;
+  uiLocked: Ref<boolean>;
+  userDict: Ref<Record<string, UserDictWord>>;
+  isOnlyHiraOrKana: Ref<boolean>;
+  accentPhrase: Ref<AccentPhrase | undefined>;
+  voiceComputed: ComputedRef<{
+    engineId: EngineId;
+    speakerId: SpeakerId;
+    styleId: StyleId;
+  }>;
+  surface: Ref<string>;
+  yomi: Ref<string>;
+  wordPriority: Ref<number>;
+  isWordChanged: ComputedRef<boolean>;
+  setYomi: (text: string, changeWord?: boolean) => Promise<void>;
+  createUILockAction: <T>(action: Promise<T>) => Promise<T>;
+  loadingDictProcess: () => Promise<void>;
+  computeRegisteredAccent: () => number;
+  discardOrNotDialog: (okCallback: () => void) => Promise<void>;
+  toInitialState: () => void;
+  toWordEditingState: () => void;
+  cancel: () => void;
+}
+</script>
+
 <script setup lang="ts">
-import { computed, ref, watch } from "vue";
+import { computed, ref, watch, provide } from "vue";
 import { QInput } from "quasar";
-import AudioAccent from "@/components/Talk/AudioAccent.vue";
-import ContextMenu from "@/components/Menu/ContextMenu/Container.vue";
-import { useRightClickContextMenu } from "@/composables/useRightClickContextMenu";
+import DictionaryEditWordDialog from "./DictionaryEditWordDialog.vue";
 import { useStore } from "@/store";
-import type { FetchAudioResult } from "@/store/type";
 import { AccentPhrase, UserDictWord } from "@/openapi";
+import { EngineId, SpeakerId, StyleId } from "@/type/preload";
 import {
   convertHiraToKana,
   convertLongVowel,
@@ -316,8 +185,6 @@ const dictionaryManageDialogOpenedComputed = computed({
   set: (val) => emit("update:modelValue", val),
 });
 const uiLocked = ref(false); // ダイアログ内でstore.getters.UI_LOCKEDは常にtrueなので独自に管理
-const nowGenerating = ref(false);
-const nowPlaying = ref(false);
 
 // word-list の要素のうち、どの要素がホバーされているか
 const hoveredKey = ref<string | undefined>(undefined);
@@ -369,18 +236,7 @@ watch(dictionaryManageDialogOpenedComputed, async (newValue) => {
 });
 
 const wordEditing = ref(false);
-
 const surfaceInput = ref<QInput>();
-const yomiInput = ref<QInput>();
-const yomiFocus = (event?: KeyboardEvent) => {
-  if (event && event.isComposing) return;
-  yomiInput.value?.focus();
-};
-const setYomiWhenEnter = (event?: KeyboardEvent) => {
-  if (event && event.isComposing) return;
-  void setYomi(yomi.value);
-};
-
 const selectedId = ref("");
 const surface = ref("");
 const yomi = ref("");
@@ -401,23 +257,7 @@ const voiceComputed = computed(() => {
 const kanaRegex = createKanaRegex();
 const isOnlyHiraOrKana = ref(true);
 const accentPhrase = ref<AccentPhrase | undefined>();
-const accentPhraseTable = ref<HTMLElement>();
-
-const convertHankakuToZenkaku = (text: string) => {
-  // " "などの目に見えない文字をまとめて全角スペース(0x3000)に置き換える
-  text = text.replace(/\p{Z}/gu, () => String.fromCharCode(0x3000));
 
-  // "!"から"~"までの範囲の文字(数字やアルファベット)を全角に置き換える
-  return text.replace(/[\u0021-\u007e]/g, (s) => {
-    return String.fromCharCode(s.charCodeAt(0) + 0xfee0);
-  });
-};
-const setSurface = (text: string) => {
-  // surfaceを全角化する
-  // 入力は半角でも問題ないが、登録時に全角に変換され、isWordChangedの判断がおかしくなることがあるので、
-  // 入力後に自動で変換するようにする
-  surface.value = convertHankakuToZenkaku(text);
-};
 const setYomi = async (text: string, changeWord?: boolean) => {
   const { engineId, styleId } = voiceComputed.value;
 
@@ -458,62 +298,6 @@ const setYomi = async (text: string, changeWord?: boolean) => {
   yomi.value = text;
 };
 
-const changeAccent = async (_: number, accent: number) => {
-  const { engineId, styleId } = voiceComputed.value;
-
-  if (accentPhrase.value) {
-    accentPhrase.value.accent = accent;
-    accentPhrase.value = (
-      await createUILockAction(
-        store.actions.FETCH_MORA_DATA({
-          accentPhrases: [accentPhrase.value],
-          engineId,
-          styleId,
-        }),
-      )
-    )[0];
-  }
-};
-
-const play = async () => {
-  if (!accentPhrase.value) return;
-
-  nowGenerating.value = true;
-  const audioItem = await store.actions.GENERATE_AUDIO_ITEM({
-    text: yomi.value,
-    voice: voiceComputed.value,
-  });
-
-  if (audioItem.query == undefined)
-    throw new Error(`assert audioItem.query !== undefined`);
-
-  audioItem.query.accentPhrases = [accentPhrase.value];
-
-  let fetchAudioResult: FetchAudioResult;
-  try {
-    fetchAudioResult = await store.actions.FETCH_AUDIO_FROM_AUDIO_ITEM({
-      audioItem,
-    });
-  } catch (e) {
-    window.backend.logError(e);
-    nowGenerating.value = false;
-    void store.actions.SHOW_ALERT_DIALOG({
-      title: "生成に失敗しました",
-      message: "エンジンの再起動をお試しください。",
-    });
-    return;
-  }
-
-  const { blob } = fetchAudioResult;
-  nowGenerating.value = false;
-  nowPlaying.value = true;
-  await store.actions.PLAY_AUDIO_BLOB({ audioBlob: blob });
-  nowPlaying.value = false;
-};
-const stop = () => {
-  void store.actions.STOP_AUDIO();
-};
-
 // accent phraseにあるaccentと実際に登録するアクセントには差が生まれる
 // アクセントが自動追加される「ガ」に指定されている場合、
 // 実際に登録するaccentの値は0となるので、そうなるように処理する
@@ -533,18 +317,13 @@ const computeDisplayAccent = () => {
 };
 
 const wordPriority = ref(defaultDictPriority);
-const wordPriorityLabels = {
-  0: "最低",
-  3: "低",
-  5: "標準",
-  7: "高",
-  10: "最高",
-};
 
 // 操作(ステートの移動)
 const isWordChanged = computed(() => {
   if (selectedId.value === "") {
-    return surface.value && yomi.value && accentPhrase.value;
+    return (
+      surface.value != "" && yomi.value != "" && accentPhrase.value != undefined
+    );
   }
   // 一旦代入することで、userDictそのものが更新された時もcomputedするようにする
   const dict = userDict.value;
@@ -557,46 +336,7 @@ const isWordChanged = computed(() => {
       dictData.priority !== wordPriority.value)
   );
 });
-const saveWord = async () => {
-  if (!accentPhrase.value) throw new Error(`accentPhrase === undefined`);
-  const accent = computeRegisteredAccent();
-  if (selectedId.value) {
-    try {
-      await store.actions.REWRITE_WORD({
-        wordUuid: selectedId.value,
-        surface: surface.value,
-        pronunciation: yomi.value,
-        accentType: accent,
-        priority: wordPriority.value,
-      });
-    } catch {
-      void store.actions.SHOW_ALERT_DIALOG({
-        title: "単語の更新に失敗しました",
-        message: "エンジンの再起動をお試しください。",
-      });
-      return;
-    }
-  } else {
-    try {
-      await createUILockAction(
-        store.actions.ADD_WORD({
-          surface: surface.value,
-          pronunciation: yomi.value,
-          accentType: accent,
-          priority: wordPriority.value,
-        }),
-      );
-    } catch {
-      void store.actions.SHOW_ALERT_DIALOG({
-        title: "単語の登録に失敗しました",
-        message: "エンジンの再起動をお試しください。",
-      });
-      return;
-    }
-  }
-  await loadingDictProcess();
-  toInitialState();
-};
+
 const deleteWord = async () => {
   const result = await store.actions.SHOW_WARNING_DIALOG({
     title: "単語を削除しますか?",
@@ -622,20 +362,7 @@ const deleteWord = async () => {
     toInitialState();
   }
 };
-const resetWord = async (id: string) => {
-  const result = await store.actions.SHOW_WARNING_DIALOG({
-    title: "単語の変更をリセットしますか?",
-    message: "単語の変更は破棄されてリセットされます。",
-    actionName: "リセットする",
-  });
-  if (result === "OK") {
-    selectedId.value = id;
-    surface.value = userDict.value[id].surface;
-    void setYomi(userDict.value[id].yomi, true);
-    wordPriority.value = userDict.value[id].priority;
-    toWordEditingState();
-  }
-};
+
 const discardOrNotDialog = async (okCallback: () => void) => {
   if (isWordChanged.value) {
     const result = await store.actions.SHOW_WARNING_DIALOG({
@@ -698,24 +425,28 @@ const toDialogClosedState = () => {
   dictionaryManageDialogOpenedComputed.value = false;
 };
 
-const surfaceContextMenu = ref<InstanceType<typeof ContextMenu>>();
-const yomiContextMenu = ref<InstanceType<typeof ContextMenu>>();
-
-const {
-  contextMenuHeader: surfaceContextMenuHeader,
-  contextMenudata: surfaceContextMenudata,
-  startContextMenuOperation: startSurfaceContextMenuOperation,
-  clearInputSelection: clearSurfaceInputSelection,
-  endContextMenuOperation: endSurfaceContextMenuOperation,
-} = useRightClickContextMenu(surfaceContextMenu, surfaceInput, surface);
-
-const {
-  contextMenuHeader: yomiContextMenuHeader,
-  contextMenudata: yomiContextMenudata,
-  startContextMenuOperation: startYomiContextMenuOperation,
-  clearInputSelection: clearYomiInputSelection,
-  endContextMenuOperation: endYomiContextMenuOperation,
-} = useRightClickContextMenu(yomiContextMenu, yomiInput, yomi);
+provide<DictionaryManageDialogContext>(dictionaryManageDialogContextKey, {
+  wordEditing,
+  surfaceInput,
+  selectedId,
+  uiLocked,
+  userDict,
+  isOnlyHiraOrKana,
+  accentPhrase,
+  voiceComputed,
+  surface,
+  yomi,
+  wordPriority,
+  isWordChanged,
+  setYomi,
+  createUILockAction,
+  loadingDictProcess,
+  computeRegisteredAccent,
+  discardOrNotDialog,
+  toInitialState,
+  toWordEditingState,
+  cancel,
+});
 </script>
 
 <style lang="scss" scoped>
@@ -780,84 +511,4 @@ const {
   position: absolute;
   z-index: 10;
 }
-
-.word-editor {
-  display: flex;
-  flex-flow: column;
-  height: calc(
-    100vh - #{vars.$menubar-height + vars.$toolbar-height +
-      vars.$window-border-width}
-  ) !important;
-  overflow: auto;
-}
-
-.word-input {
-  padding-left: 10px;
-  width: calc(66vw - 80px);
-
-  :deep(.q-field__control) {
-    height: 2rem;
-  }
-
-  :deep(.q-placeholder) {
-    padding: 0;
-    font-size: 20px;
-  }
-
-  :deep(.q-field__after) {
-    height: 2rem;
-  }
-}
-
-.desc-row {
-  color: rgba(colors.$display-rgb, 0.5);
-  font-size: 12px;
-}
-
-.play-button {
-  margin: auto 0;
-  padding-right: 16px;
-}
-
-.accent-phrase-table {
-  flex-grow: 1;
-  align-self: stretch;
-
-  display: flex;
-  height: 130px;
-  overflow-x: scroll;
-  width: calc(66vw - 140px);
-
-  .mora-table {
-    display: inline-grid;
-    align-self: stretch;
-    grid-template-rows: 1fr 60px 30px;
-
-    .text-cell {
-      padding: 0;
-      min-width: 20px;
-      max-width: 20px;
-      grid-row-start: 3;
-      text-align: center;
-      white-space: nowrap;
-      color: colors.$display;
-      position: relative;
-    }
-
-    .splitter-cell {
-      min-width: 20px;
-      max-width: 20px;
-      grid-row: 3 / span 1;
-      z-index: vars.$detail-view-splitter-cell-z-index;
-    }
-  }
-}
-
-.save-delete-reset-buttons {
-  padding: 20px;
-
-  display: flex;
-  flex: 1;
-  align-items: flex-end;
-}
 </style>