diff --git a/app/components/editors/SongEditor.vue b/app/components/editors/SongEditor.vue
index c368cac7..e8d5f865 100644
--- a/app/components/editors/SongEditor.vue
+++ b/app/components/editors/SongEditor.vue
@@ -1,5 +1,6 @@
+
+
+
+
+ {{ $t("lyrics.editLyrics") }}
+
+
+
+
+ {{ title }}
+
+
+
+ {{ artists }}
+
+
+
+ {{ tags }}
+
+
+
+
+
diff --git a/app/composables/validator.ts b/app/composables/validator.ts
new file mode 100644
index 00000000..fdf03693
--- /dev/null
+++ b/app/composables/validator.ts
@@ -0,0 +1,58 @@
+import type { MaruSongDataParsed } from '@marure/schema'
+import type { Ref } from 'vue'
+import { useNuxtApp } from 'nuxt/app'
+import { reactive } from 'vue'
+
+export interface ValidationErrors {
+ youtube: string
+ title: string
+ artists: string
+ tags: string
+ offset?: string
+ notes?: string
+}
+
+export function useValidation(stateRef: MaruSongDataParsed) {
+ const errors = reactive({
+ youtube: '',
+ title: '',
+ artists: '',
+ tags: '',
+ })
+ const nuxtApp = useNuxtApp()
+ const { t } = nuxtApp.$i18n
+
+ const validate = (): boolean => {
+ let isValid = true
+
+ errors.youtube = ''
+ errors.title = ''
+ errors.artists = ''
+ errors.tags = ''
+
+ if (!stateRef.youtube || !stateRef.youtube.trim()) {
+ errors.youtube = t('editor.validator.youtube')
+ isValid = false
+ }
+
+ if (!stateRef.title || !stateRef.title.trim()) {
+ errors.title = t('editor.validator.title')
+ isValid = false
+ }
+ if (!stateRef.artists || stateRef.artists?.includes('')) {
+ errors.artists = t('editor.validator.artists')
+ isValid = false
+ }
+
+ if (!stateRef.tags || stateRef.tags?.includes('')) {
+ errors.tags = t('editor.validator.tags')
+ isValid = false
+ }
+ return isValid
+ }
+
+ return {
+ errors,
+ validate,
+ }
+}
diff --git a/app/locales/en.json b/app/locales/en.json
index 42aee7ae..860c6321 100644
--- a/app/locales/en.json
+++ b/app/locales/en.json
@@ -48,7 +48,13 @@
"setCurrentTime": "Apply current playback time and go next",
"setCurrentTimeOnly": "Apply current playback time"
},
- "visualization": "Visualization"
+ "visualization": "Visualization",
+ "validator": {
+ "youtube": "YouTube ID is required.",
+ "title": "Title is required.",
+ "artists": "At least one artist is required.",
+ "tags": "At least one tag is required."
+ }
},
"footer": {
"fileFormat": "File Format",
diff --git a/app/locales/ja.json b/app/locales/ja.json
index 2bd7ecc0..3c5748a2 100644
--- a/app/locales/ja.json
+++ b/app/locales/ja.json
@@ -48,7 +48,13 @@
"setCurrentTime": "現在の再生時間に設定して次の行へ",
"setCurrentTimeOnly": "現在の再生時間に設定"
},
- "visualization": "可視化"
+ "visualization": "可視化",
+ "validator": {
+ "youtube": "YouTube IDは必須です。",
+ "title": "タイトルは必須です。",
+ "artists": "少なくとも1人のアーティストが必要です。",
+ "tags": "少なくとも1つのタグが必要です。"
+ }
},
"footer": {
"fileFormat": "ファイル形式",
diff --git a/app/locales/zh-Hans.json b/app/locales/zh-Hans.json
index db7370f3..65795da8 100644
--- a/app/locales/zh-Hans.json
+++ b/app/locales/zh-Hans.json
@@ -48,7 +48,14 @@
"setCurrentTime": "设为当前播放时间后跳行",
"setCurrentTimeOnly": "设为当前播放时间"
},
- "visualization": "可视化"
+ "visualization": "可视化",
+ "validator": {
+ "youtube": "YouTube ID 是必填的。",
+ "title": "标题是必填的。",
+ "artists": "至少需要一个歌手。",
+ "tags": "至少需要一个标签。"
+ }
+
},
"footer": {
"fileFormat": "文件格式",
diff --git a/app/locales/zh-Hant.json b/app/locales/zh-Hant.json
index cf9036d9..0f6ea2e1 100644
--- a/app/locales/zh-Hant.json
+++ b/app/locales/zh-Hant.json
@@ -48,7 +48,13 @@
"setCurrentTime": "設為當前播放時間後跳行",
"setCurrentTimeOnly": "設為當前播放時間"
},
- "visualization": "可視化"
+ "visualization": "可視化",
+ "validator": {
+ "youtube": "YouTube ID 是必填的。",
+ "title": "標題是必填的。",
+ "artists": "至少需要一個歌手。",
+ "tags": "至少需要一個標籤。"
+ }
},
"footer": {
"fileFormat": "檔案格式",