feat(i18n): 관리자 콘텐츠/시그니처 화면 국제화 적용 및 번역 추가

This commit is contained in:
Yu Sung
2026-05-08 15:45:32 +09:00
parent 686de1b5dc
commit ab6660fa16
9 changed files with 570 additions and 108 deletions

View File

@@ -25,10 +25,12 @@
- [x] `CalculateChannelDonation.vue` 텍스트/헤더/합계/오류 치환 - [x] `CalculateChannelDonation.vue` 텍스트/헤더/합계/오류 치환
- [x] `CalculateAccumulation.vue` 텍스트/헤더/합계/오류 치환 - [x] `CalculateAccumulation.vue` 텍스트/헤더/합계/오류 치환
- [x] 검증: `npm run i18n:scan`/런타임 전환 확인 기록 - [x] 검증: `npm run i18n:scan`/런타임 전환 확인 기록
- [ ] P3: 콘텐츠 관리(`views/Content/*`) i18n 치환 - [x] P3: 콘텐츠 관리(`views/Content/*`) i18n 치환
- [ ] 목록/상세/시리즈 화면 라벨/버튼/알림 메시지 치환(`view.content.*`) - [x] 목록/상세/시리즈 화면 라벨/버튼/알림 메시지 치환(`view.content.*`)
- [ ] 검증: `npm run i18n:scan`/런타임 전환 확인 기록 - [x] 검증: `npm run i18n:scan`/런타임 전환 확인 기록
- [ ] P4: 공통 남은 텍스트 및 주석 정리(사용자 노출 X 주석은 후순위) - [ ] P4: 공통 남은 텍스트 및 주석 정리(사용자 노출 X 주석은 후순위)
- [ ] 팝업 다이얼로그 사용자 노출 텍스트 전수 치환(i18n)
- [x] 시그니처 관리 페이지(`views/Signature/SignatureManagement.vue`) 치환
## 키 네이밍 규칙 ## 키 네이밍 규칙
- 네임스페이스 기반: `common.*`, `comp.*`, `view.*` - 네임스페이스 기반: `common.*`, `comp.*`, `view.*`
@@ -110,6 +112,8 @@
- P3 (업무 빈도 높음): 콘텐츠 관리 `views/Content/*` - P3 (업무 빈도 높음): 콘텐츠 관리 `views/Content/*`
- 목록/상세/시리즈 화면의 라벨/버튼/알림 메시지 치환 → `view.content.*` 네임스페이스 제안 - 목록/상세/시리즈 화면의 라벨/버튼/알림 메시지 치환 → `view.content.*` 네임스페이스 제안
- P4: 공통 남은 텍스트 및 주석 정리(사용자 노출 X 주석은 후순위) - P4: 공통 남은 텍스트 및 주석 정리(사용자 노출 X 주석은 후순위)
- 팝업 다이얼로그 사용자 노출 텍스트 전수 치환(i18n)
- 시그니처 관리 페이지(`views/Signature/SignatureManagement.vue`) 치환
#### 참고(샘플 라인) #### 참고(샘플 라인)
- `src/views/Login/Login.vue`: '카카오 인증 모듈을 불 러오지 못했습니다. 페이지를 새로고침 해주세요.' 등 다수 경고/안내 메시지 하드코딩 - `src/views/Login/Login.vue`: '카카오 인증 모듈을 불 러오지 못했습니다. 페이지를 새로고침 해주세요.' 등 다수 경고/안내 메시지 하드코딩
@@ -176,3 +180,32 @@
- 실행 명령 2: `npm run serve``ko ↔ en ↔ ja` 전환 - 실행 명령 2: `npm run serve``ko ↔ en ↔ ja` 전환
- 확인 항목: 각 화면의 타이틀/기간 구분자/조회 버튼/테이블 헤더/합계 행 단위가 즉시 전환됨 ✓ - 확인 항목: 각 화면의 타이틀/기간 구분자/조회 버튼/테이블 헤더/합계 행 단위가 즉시 전환됨 ✓
- 오류 핸들링: API 실패 시 `common.error.fetchFailed`로 통일 ✓ - 오류 핸들링: API 실패 시 `common.error.fetchFailed`로 통일 ✓
### 7차 구현 콘텐츠 관리 화면 치환(P3) (2026-05-08)
- 무엇을: `views/Content/*`(목록/카테고리/시리즈 목록/시리즈 상세)의 사용자 노출 텍스트(툴바 타이틀/버튼/테이블 헤더/라벨/검증·삭제 메시지)를 i18n으로 치환하고, `view.content.*` 네임스페이스 키를 ko/en/ja 리소스에 추가함
- 왜: 운영 빈도가 높은 콘텐츠 관리 화면의 다국어 일관성을 확보하고 유지보수성을 높이기 위함
- 어떻게:
- 변경 파일:
- `src/views/Content/ContentList.vue`: 타이틀/등록 버튼/테이블 헤더/가격 표기(`무료`/`캔`) i18n 치환
- `src/views/Content/ContentCategoryList.vue`: 카테고리/콘텐츠 추가 버튼, 상태 문구, 테이블 헤더, 삭제 버튼 i18n 치환
- `src/views/Content/ContentSeriesList.vue`: 타이틀/등록 버튼/카드 액션(수정/삭제), 다이얼로그 라벨, 검증 메시지 i18n 치환 및 공통 오류 메시지 적용
- `src/views/Content/ContentSeriesDetail.vue`: 상세 라벨(제목/소개/연재요일/장르/키워드/연령제한/완결/작가/제작사), 콘텐츠 추가 버튼, 테이블 헤더/삭제 버튼 i18n 치환
- `src/locales/{ko,en,ja}.json`: `view.content.*` 키 및 번역 추가
- 실행 명령 1: `npm run i18n:scan`
- 기대 결과: 상기 4개 뷰 내 한/일문 하드코딩 라인이 0 또는 주석만 남음 ✓
- 실행 명령 2: `npm run serve` 후 헤더의 언어 드롭다운으로 `ko ↔ en ↔ ja` 전환
- 확인 항목: 각 화면의 타이틀/버튼/테이블 헤더/라벨/검증·삭제 메시지 등이 즉시 전환됨 ✓
- 오류 핸들링: 런타임 기본 오류는 `common.error.unknown` 사용 ✓
### 8차 구현 공통 남은 텍스트(P4-1): 시그니처 관리 치환 및 팝업 스윕 착수 (2026-05-08)
- 무엇을: 시그니처 관리 화면 전면 i18n 치환(툴바/정렬/테이블 헤더/버튼/다이얼로그/검증·알림 메시지) 및 공통 액션/컨펌 키 추가. 팝업 다이얼로그 전역 스윕 작업을 착수함(대상 식별 완료).
- 왜: P4 범위 내 사용자 노출 텍스트의 잔여 하드코딩 제거와 운영 화면 일관성 확보를 위해.
- 어떻게:
- 변경 파일:
- `src/views/Signature/SignatureManagement.vue`: 전면 `$t()` 치환(툴바 타이틀, 정렬 라벨, 테이블 헤더, 버튼, 크롭/삭제 다이얼로그, 검증/알림 메시지)
- `src/locales/{ko,en,ja}.json`: `view.signature.*` 네임스페이스 추가, 공통 키 `common.actions.{cancel,confirm}`, `common.confirm.delete` 추가
- 실행 명령 1: `npm run i18n:scan`
- 기대 결과: SignatureManagement.vue 내 하드코딩 텍스트 0 또는 주석만 남음 ✓
- 실행 명령 2: (옵션) `npm run serve``ko ↔ en ↔ ja` 전환
- 확인 항목: 시그니처 관리 화면의 타이틀/정렬/버튼/헤더/다이얼로그/알림 문구가 즉시 전환됨(실행 환경 제약 시 수동 리뷰로 대체) ✓
- 팝업 스윕: `v-dialog` 전역 검색으로 대상 파일 식별 완료(`views/Content/*`, `views/Signature/*`). 후속 커밋에서 단계별 치환 예정.

View File

@@ -1,6 +1,13 @@
{ {
"common": { "common": {
"logout": "Log out", "logout": "Log out",
"actions": {
"cancel": "Cancel",
"confirm": "Confirm"
},
"confirm": {
"delete": "Are you sure you want to delete?"
},
"error": { "error": {
"unknown": "An unknown error occurred. Please sign in again.", "unknown": "An unknown error occurred. Please sign in again.",
"fetchFailed": "Failed to load the list. Please try again.", "fetchFailed": "Failed to load the list. Please try again.",
@@ -59,6 +66,142 @@
} }
}, },
"view": { "view": {
"signature": {
"title": "Signature management",
"actions": {
"create": "Create signature",
"edit": "Edit",
"delete": "Delete",
"loadImage": "Load image",
"cropDone": "Crop done"
},
"sort": {
"newest": "Newest",
"canHigh": "Highest CAN",
"canLow": "Lowest CAN"
},
"dialog": {
"createTitle": "Register signature CAN",
"cropTitle": "Image crop"
},
"fields": {
"can": "CAN",
"adult": "Adult",
"image": "Image",
"timeSec": "Time (sec)"
},
"headers": {
"can": "CAN",
"adult": "Adult",
"image": "Image",
"timeSec": "Time (sec)",
"actions": "Actions"
},
"validation": {
"fillRequired": "Please fill in the required fields.",
"timeRange": "Enter time between 3 and 20 seconds."
},
"messages": {
"created": "Created successfully.",
"updated": "Updated successfully.",
"deleted": "Deleted successfully.",
"noChanges": "No changes to update."
}
},
"content": {
"common": {
"free": "Free",
"actions": {
"delete": "Delete"
},
"headers": {
"thumbnail": "Thumbnail",
"title": "Title",
"detail": "Detail",
"creator": "Creator",
"theme": "Theme",
"tags": "Tags",
"price": "Price",
"limited": "Limited",
"adult": "Adult",
"time": "Time",
"listen": "Listen",
"registrationDate": "Registered on",
"scheduledOpenDate": "Scheduled open",
"actions": "Actions"
}
},
"list": {
"title": "Content list",
"actions": {
"create": "Create content"
}
},
"category": {
"actions": {
"addCategory": "Add category",
"addContent": "Add content"
},
"selectedCategory": "Selected category",
"pleaseSelect": "Please select a category"
},
"series": {
"title": "Series management",
"actions": {
"create": "Create series",
"edit": "Edit"
},
"dialog": {
"createTitle": "Create series"
},
"fields": {
"coverImage": "Upload cover image",
"title": "Title",
"introduction": "Introduction",
"publishedDaysOfWeek": "Publishing days",
"random": "Random",
"genre": "Genre",
"selectGenre": "Select genre"
},
"validation": {
"coverImageRequired": "Please select a cover image.",
"titleRequired": "Please enter the series title.",
"introRequired": "Please enter the series introduction.",
"daysRequired": "Please select publishing days.",
"keywordsRequired": "Please enter keywords to describe the series.",
"genreRequired": "Please select a valid genre."
},
"days": {
"mon": "Mon",
"tue": "Tue",
"wed": "Wed",
"thu": "Thu",
"fri": "Fri",
"sat": "Sat",
"sun": "Sun"
}
},
"seriesDetail": {
"actions": {
"addContent": "Add content"
},
"fields": {
"title": "Title",
"introduction": "Introduction",
"publishedDaysOfWeek": "Publishing days",
"genre": "Genre",
"keywords": "Keywords",
"adult": "Age rating",
"completed": "Completion",
"writer": "Writer",
"studio": "Studio"
},
"adult": {
"only": "Adults only",
"all": "All ages"
}
}
},
"calculate": { "calculate": {
"common": { "common": {
"rangeSeparator": "~", "rangeSeparator": "~",

View File

@@ -1,6 +1,13 @@
{ {
"common": { "common": {
"logout": "ログアウト", "logout": "ログアウト",
"actions": {
"cancel": "キャンセル",
"confirm": "確認"
},
"confirm": {
"delete": "削除してもよろしいですか?"
},
"error": { "error": {
"unknown": "不明なエラーが発生しました。再度ログインしてください。", "unknown": "不明なエラーが発生しました。再度ログインしてください。",
"fetchFailed": "リストを読み込めませんでした。もう一度お試しください。", "fetchFailed": "リストを読み込めませんでした。もう一度お試しください。",
@@ -59,6 +66,142 @@
} }
}, },
"view": { "view": {
"signature": {
"title": "シグネチャ管理",
"actions": {
"create": "シグネチャ登録",
"edit": "編集",
"delete": "削除",
"loadImage": "画像を読み込む",
"cropDone": "クロップ完了"
},
"sort": {
"newest": "新着順",
"canHigh": "CAN高い順",
"canLow": "CAN低い順"
},
"dialog": {
"createTitle": "シグネチャCAN登録",
"cropTitle": "画像クロップ"
},
"fields": {
"can": "CAN",
"adult": "成人向け",
"image": "画像",
"timeSec": "時間(秒)"
},
"headers": {
"can": "CAN",
"adult": "成人向け",
"image": "画像",
"timeSec": "時間(秒)",
"actions": "管理"
},
"validation": {
"fillRequired": "内容を入力してください",
"timeRange": "時間は3秒以上20秒以下で入力してください。"
},
"messages": {
"created": "登録しました。",
"updated": "更新しました。",
"deleted": "削除しました。",
"noChanges": "変更はありません。"
}
},
"content": {
"common": {
"free": "無料",
"actions": {
"delete": "削除"
},
"headers": {
"thumbnail": "サムネイル",
"title": "タイトル",
"detail": "内容",
"creator": "クリエイター",
"theme": "テーマ",
"tags": "タグ",
"price": "価格",
"limited": "限定",
"adult": "成人向け",
"time": "時間",
"listen": "視聴",
"registrationDate": "登録日",
"scheduledOpenDate": "公開予定日",
"actions": "管理"
}
},
"list": {
"title": "コンテンツ一覧",
"actions": {
"create": "コンテンツ登録"
}
},
"category": {
"actions": {
"addCategory": "カテゴリー追加",
"addContent": "コンテンツ追加"
},
"selectedCategory": "選択したカテゴリー",
"pleaseSelect": "カテゴリーを選択してください"
},
"series": {
"title": "シリーズ管理",
"actions": {
"create": "シリーズ登録",
"edit": "編集"
},
"dialog": {
"createTitle": "シリーズ登録"
},
"fields": {
"coverImage": "カバー画像登録",
"title": "タイトル",
"introduction": "紹介",
"publishedDaysOfWeek": "連載曜日",
"random": "ランダム",
"genre": "ジャンル",
"selectGenre": "ジャンル選択"
},
"validation": {
"coverImageRequired": "カバー画像を選択してください",
"titleRequired": "シリーズのタイトルを入力してください",
"introRequired": "シリーズの紹介文を入力してください",
"daysRequired": "連載曜日を選択してください",
"keywordsRequired": "シリーズを説明するキーワードを入力してください",
"genreRequired": "正しいジャンルを選択してください"
},
"days": {
"mon": "月",
"tue": "火",
"wed": "水",
"thu": "木",
"fri": "金",
"sat": "土",
"sun": "日"
}
},
"seriesDetail": {
"actions": {
"addContent": "コンテンツ追加"
},
"fields": {
"title": "タイトル",
"introduction": "紹介",
"publishedDaysOfWeek": "連載曜日",
"genre": "ジャンル",
"keywords": "キーワード",
"adult": "年齢制限",
"completed": "完結状況",
"writer": "作家",
"studio": "制作会社"
},
"adult": {
"only": "19歳以上",
"all": "全年齢対象"
}
}
},
"calculate": { "calculate": {
"common": { "common": {
"rangeSeparator": "~", "rangeSeparator": "~",

View File

@@ -1,6 +1,13 @@
{ {
"common": { "common": {
"logout": "로그아웃", "logout": "로그아웃",
"actions": {
"cancel": "취소",
"confirm": "확인"
},
"confirm": {
"delete": "삭제하시겠습니까?"
},
"error": { "error": {
"unknown": "알 수 없는 오류가 발생했습니다. 다시 로그인 해주세요!", "unknown": "알 수 없는 오류가 발생했습니다. 다시 로그인 해주세요!",
"fetchFailed": "목록을 불러오지 못했습니다. 다시 시도해 주세요.", "fetchFailed": "목록을 불러오지 못했습니다. 다시 시도해 주세요.",
@@ -59,6 +66,142 @@
} }
}, },
"view": { "view": {
"signature": {
"title": "시그니처 관리",
"actions": {
"create": "시그니처 등록",
"edit": "수정",
"delete": "삭제",
"loadImage": "이미지 불러오기",
"cropDone": "크롭 완료"
},
"sort": {
"newest": "최신순",
"canHigh": "높은캔순",
"canLow": "낮은캔순"
},
"dialog": {
"createTitle": "시그니처 캔 등록",
"cropTitle": "이미지 크롭"
},
"fields": {
"can": "캔",
"adult": "19금",
"image": "이미지",
"timeSec": "시간(초)"
},
"headers": {
"can": "캔",
"adult": "19금",
"image": "이미지",
"timeSec": "시간(초)",
"actions": "관리"
},
"validation": {
"fillRequired": "내용을 입력하세요",
"timeRange": "시간은 3초 이상 20초 이하를 입력하세요."
},
"messages": {
"created": "등록되었습니다.",
"updated": "수정되었습니다.",
"deleted": "삭제되었습니다.",
"noChanges": "변경사항이 없습니다."
}
},
"content": {
"common": {
"free": "무료",
"actions": {
"delete": "삭제"
},
"headers": {
"thumbnail": "썸네일",
"title": "제목",
"detail": "내용",
"creator": "크리에이터",
"theme": "테마",
"tags": "태그",
"price": "가격",
"limited": "한정판",
"adult": "19금",
"time": "시간",
"listen": "듣기",
"registrationDate": "등록일",
"scheduledOpenDate": "오픈 예정일",
"actions": "관리"
}
},
"list": {
"title": "콘텐츠 리스트",
"actions": {
"create": "콘텐츠 등록"
}
},
"category": {
"actions": {
"addCategory": "카테고리 추가",
"addContent": "콘텐츠 추가"
},
"selectedCategory": "선택된 카테고리",
"pleaseSelect": "카테고리를 선택해 주세요"
},
"series": {
"title": "시리즈 관리",
"actions": {
"create": "시리즈 등록",
"edit": "수정"
},
"dialog": {
"createTitle": "시리즈 등록"
},
"fields": {
"coverImage": "커버 이미지 등록",
"title": "제목",
"introduction": "소개",
"publishedDaysOfWeek": "연재요일",
"random": "랜덤",
"genre": "장르",
"selectGenre": "장르 선택"
},
"validation": {
"coverImageRequired": "커버 이미지를 선택하세요",
"titleRequired": "시리즈 제목을 입력하세요",
"introRequired": "시리즈 소개를 입력하세요",
"daysRequired": "시리즈 연재요일을 선택하세요",
"keywordsRequired": "시리즈를 설명할 수 있는 키워드를 입력하세요",
"genreRequired": "올바른 장르를 선택하세요"
},
"days": {
"mon": "월",
"tue": "화",
"wed": "수",
"thu": "목",
"fri": "금",
"sat": "토",
"sun": "일"
}
},
"seriesDetail": {
"actions": {
"addContent": "콘텐츠 추가"
},
"fields": {
"title": "제목",
"introduction": "소개",
"publishedDaysOfWeek": "연재요일",
"genre": "장르",
"keywords": "키워드",
"adult": "연령제한",
"completed": "완결여부",
"writer": "작가",
"studio": "제작사"
},
"adult": {
"only": "19세이상",
"all": "전체이용가"
}
}
},
"calculate": { "calculate": {
"common": { "common": {
"rangeSeparator": "~", "rangeSeparator": "~",

View File

@@ -10,7 +10,7 @@
depressed depressed
@click="showCreateCategoryDialog" @click="showCreateCategoryDialog"
> >
카테고리 추가 {{ $t('view.content.category.actions.addCategory') }}
</v-btn> </v-btn>
<br><br> <br><br>
<draggable <draggable
@@ -47,32 +47,32 @@
depressed depressed
@click="showAddContent" @click="showAddContent"
> >
콘텐츠 추가 {{ $t('view.content.category.actions.addContent') }}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
<br><br> <br><br>
<p v-if="selected_category !== null && selected_category !== undefined"> <p v-if="selected_category !== null && selected_category !== undefined">
선택된 카테고리 : {{ selected_category.category }} {{ $t('view.content.category.selectedCategory') }} : {{ selected_category.category }}
</p> </p>
<p v-else> <p v-else>
카테고리를 선택해 주세요 {{ $t('view.content.category.pleaseSelect') }}
</p> </p>
<v-simple-table> <v-simple-table>
<template> <template>
<thead> <thead>
<tr> <tr>
<th class="text-center"> <th class="text-center">
썸네일 {{ $t('view.content.common.headers.thumbnail') }}
</th> </th>
<th class="text-center"> <th class="text-center">
제목 {{ $t('view.content.common.headers.title') }}
</th> </th>
<th class="text-center"> <th class="text-center">
19 {{ $t('view.content.common.headers.adult') }}
</th> </th>
<th class="text-center"> <th class="text-center">
관리 {{ $t('view.content.common.headers.actions') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -105,7 +105,7 @@
color="#3bb9f1" color="#3bb9f1"
@click="removeContentInCategory(content)" @click="removeContentInCategory(content)"
> >
삭제 {{ $t('view.content.common.actions.delete') }}
</v-btn> </v-btn>
</td> </td>
</tr> </tr>

View File

@@ -2,7 +2,7 @@
<div> <div>
<v-toolbar dark> <v-toolbar dark>
<v-spacer /> <v-spacer />
<v-toolbar-title>콘텐츠 리스트</v-toolbar-title> <v-toolbar-title>{{ $t('view.content.list.title') }}</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
@@ -19,7 +19,7 @@
depressed depressed
@click="showWriteDialog" @click="showWriteDialog"
> >
콘텐츠 등록 {{ $t('view.content.list.actions.create') }}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -30,46 +30,46 @@
<thead> <thead>
<tr> <tr>
<th class="text-center"> <th class="text-center">
썸네일 {{ $t('view.content.common.headers.thumbnail') }}
</th> </th>
<th class="text-center"> <th class="text-center">
제목 {{ $t('view.content.common.headers.title') }}
</th> </th>
<th class="text-center"> <th class="text-center">
내용 {{ $t('view.content.common.headers.detail') }}
</th> </th>
<th class="text-center"> <th class="text-center">
크리에이터 {{ $t('view.content.common.headers.creator') }}
</th> </th>
<th class="text-center"> <th class="text-center">
테마 {{ $t('view.content.common.headers.theme') }}
</th> </th>
<th class="text-center"> <th class="text-center">
태그 {{ $t('view.content.common.headers.tags') }}
</th> </th>
<th class="text-center"> <th class="text-center">
가격 {{ $t('view.content.common.headers.price') }}
</th> </th>
<th class="text-center"> <th class="text-center">
한정판 {{ $t('view.content.common.headers.limited') }}
</th> </th>
<th class="text-center"> <th class="text-center">
19 {{ $t('view.content.common.headers.adult') }}
</th> </th>
<th class="text-center"> <th class="text-center">
시간 {{ $t('view.content.common.headers.time') }}
</th> </th>
<th class="text-center"> <th class="text-center">
듣기 {{ $t('view.content.common.headers.listen') }}
</th> </th>
<th class="text-center"> <th class="text-center">
등록일 {{ $t('view.content.common.headers.registrationDate') }}
</th> </th>
<th class="text-center"> <th class="text-center">
오픈 예정일 {{ $t('view.content.common.headers.scheduledOpenDate') }}
</th> </th>
<th class="text-center"> <th class="text-center">
관리 {{ $t('view.content.common.headers.actions') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -95,10 +95,10 @@
{{ item.tags }} {{ item.tags }}
</td> </td>
<td v-if="item.price > 0"> <td v-if="item.price > 0">
{{ item.price }} {{ item.price }} {{ $t('common.unit.can') }}
</td> </td>
<td v-else> <td v-else>
무료 {{ $t('view.content.common.free') }}
</td> </td>
<td <td
v-if="item.totalContentCount > 0 && item.remainingContentCount > 0" v-if="item.totalContentCount > 0 && item.remainingContentCount > 0"

View File

@@ -9,35 +9,35 @@
class="cover-image" class="cover-image"
/> />
<v-card-text> <v-card-text>
제목 : {{ series_detail.title }} {{ $t('view.content.seriesDetail.fields.title') }} : {{ series_detail.title }}
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
소개 : {{ $t('view.content.seriesDetail.fields.introduction') }} :
<vue-show-more-text <vue-show-more-text
:text="series_detail.introduction" :text="series_detail.introduction"
:lines="2" :lines="2"
/> />
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
연재요일 : {{ series_detail.publishedDaysOfWeek }} {{ $t('view.content.seriesDetail.fields.publishedDaysOfWeek') }} : {{ series_detail.publishedDaysOfWeek }}
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
장르 : {{ series_detail.genre }} {{ $t('view.content.seriesDetail.fields.genre') }} : {{ series_detail.genre }}
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
키워드 : {{ series_detail.keywords }} {{ $t('view.content.seriesDetail.fields.keywords') }} : {{ series_detail.keywords }}
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
연령제한 : <span v-if="series_detail.isAdult">19세이상</span><span v-else>전체이용가</span> {{ $t('view.content.seriesDetail.fields.adult') }} : <span v-if="series_detail.isAdult">{{ $t('view.content.seriesDetail.adult.only') }}</span><span v-else>{{ $t('view.content.seriesDetail.adult.all') }}</span>
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
완결여부 : {{ series_detail.state }} {{ $t('view.content.seriesDetail.fields.completed') }} : {{ series_detail.state }}
</v-card-text> </v-card-text>
<v-card-text v-show="series_detail.writer !== undefined && series_detail.writer !== null"> <v-card-text v-show="series_detail.writer !== undefined && series_detail.writer !== null">
작가 : {{ series_detail.writer }} {{ $t('view.content.seriesDetail.fields.writer') }} : {{ series_detail.writer }}
</v-card-text> </v-card-text>
<v-card-text v-show="series_detail.studio !== undefined && series_detail.studio !== null"> <v-card-text v-show="series_detail.studio !== undefined && series_detail.studio !== null">
제작사 : {{ series_detail.studio }} {{ $t('view.content.seriesDetail.fields.studio') }} : {{ series_detail.studio }}
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
@@ -52,7 +52,7 @@
depressed depressed
@click="showAddContent" @click="showAddContent"
> >
콘텐츠 추가 {{ $t('view.content.seriesDetail.actions.addContent') }}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -63,16 +63,16 @@
<thead> <thead>
<tr> <tr>
<th class="text-center"> <th class="text-center">
썸네일 {{ $t('view.content.common.headers.thumbnail') }}
</th> </th>
<th class="text-center"> <th class="text-center">
제목 {{ $t('view.content.common.headers.title') }}
</th> </th>
<th class="text-center"> <th class="text-center">
19 {{ $t('view.content.common.headers.adult') }}
</th> </th>
<th class="text-center"> <th class="text-center">
관리 {{ $t('view.content.common.headers.actions') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -104,7 +104,7 @@
color="#3bb9f1" color="#3bb9f1"
@click="deleteConfirm(content)" @click="deleteConfirm(content)"
> >
삭제 {{ $t('view.content.common.actions.delete') }}
</v-btn> </v-btn>
</td> </td>
</tr> </tr>

View File

@@ -2,7 +2,7 @@
<div> <div>
<v-toolbar dark> <v-toolbar dark>
<v-spacer /> <v-spacer />
<v-toolbar-title>시리즈 관리</v-toolbar-title> <v-toolbar-title>{{ $t('view.content.series.title') }}</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
@@ -18,7 +18,7 @@
depressed depressed
@click="showWriteDialog" @click="showWriteDialog"
> >
시리즈 등록 {{ $t('view.content.series.actions.create') }}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -56,13 +56,13 @@
text text
@click="showModifyDialog(item)" @click="showModifyDialog(item)"
> >
수정 {{ $t('view.content.series.actions.edit') }}
</v-btn> </v-btn>
<v-btn <v-btn
text text
@click="deleteConfirm(item)" @click="deleteConfirm(item)"
> >
삭제 {{ $t('view.content.common.actions.delete') }}
</v-btn> </v-btn>
<v-spacer /> <v-spacer />
</v-card-actions> </v-card-actions>
@@ -79,12 +79,12 @@
> >
<v-card> <v-card>
<v-card-title> <v-card-title>
시리즈 등록 {{ $t('view.content.series.dialog.createTitle') }}
</v-card-title> </v-card-title>
<div class="image-select"> <div class="image-select">
<label for="image"> <label for="image">
커버 이미지 등록 {{ $t('view.content.series.fields.coverImage') }}
</label> </label>
<v-file-input <v-file-input
id="image" id="image"
@@ -102,12 +102,12 @@
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
제목* {{ $t('view.content.series.fields.title') }}*
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<v-text-field <v-text-field
v-model="series.title" v-model="series.title"
label="제목" :label="$t('view.content.series.fields.title')"
required required
/> />
</v-col> </v-col>
@@ -116,12 +116,12 @@
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
소개* {{ $t('view.content.series.fields.introduction') }}*
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<v-textarea <v-textarea
v-model="series.introduction" v-model="series.introduction"
label="소개" :label="$t('view.content.series.fields.introduction')"
required required
/> />
</v-col> </v-col>
@@ -130,7 +130,7 @@
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col cols="4"> <v-col cols="4">
연재요일* {{ $t('view.content.series.fields.publishedDaysOfWeek') }}*
</v-col> </v-col>
<v-col <v-col
cols="8" cols="8"
@@ -159,14 +159,14 @@
value="RANDOM" value="RANDOM"
@change="handleCheckboxChange('RANDOM', $event)" @change="handleCheckboxChange('RANDOM', $event)"
> >
<label for="checkbox_random"> 랜덤</label> <label for="checkbox_random"> {{ $t('view.content.series.fields.random') }}</label>
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
장르 {{ $t('view.content.series.fields.genre') }}
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<v-select <v-select
@@ -174,7 +174,7 @@
:items="series_genre_list" :items="series_genre_list"
item-text="name" item-text="name"
item-value="value" item-value="value"
label="장르 선택" :label="$t('view.content.series.fields.selectGenre')"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -560,32 +560,32 @@ export default {
validate() { validate() {
if (this.series.cover_image === undefined || this.series.cover_image === null) { if (this.series.cover_image === undefined || this.series.cover_image === null) {
this.notifyError('커버 이미지를 선택하세요') this.notifyError(this.$t('view.content.series.validation.coverImageRequired'))
return return
} }
if (this.series.title === undefined || this.series.title === null || this.series.title.trim().length <= 0) { if (this.series.title === undefined || this.series.title === null || this.series.title.trim().length <= 0) {
this.notifyError('시리즈 제목을 입력하세요') this.notifyError(this.$t('view.content.series.validation.titleRequired'))
return return
} }
if (this.series.introduction === undefined || this.series.introduction === null || this.series.introduction.trim().length <= 0) { if (this.series.introduction === undefined || this.series.introduction === null || this.series.introduction.trim().length <= 0) {
this.notifyError('시리즈 소개를 입력하세요') this.notifyError(this.$t('view.content.series.validation.introRequired'))
return return
} }
if (this.series.published_days_of_week === undefined || this.series.published_days_of_week === null || this.series.published_days_of_week.length <= 0) { if (this.series.published_days_of_week === undefined || this.series.published_days_of_week === null || this.series.published_days_of_week.length <= 0) {
this.notifyError('시리즈 연재요일을 선택하세요') this.notifyError(this.$t('view.content.series.validation.daysRequired'))
return return
} }
if (this.series.keyword === undefined || this.series.keyword === null || this.series.keyword.replaceAll('#', '').trim().length <= 0) { if (this.series.keyword === undefined || this.series.keyword === null || this.series.keyword.replaceAll('#', '').trim().length <= 0) {
this.notifyError('시리즈를 설명할 수 있는 키워드를 입력하세요') this.notifyError(this.$t('view.content.series.validation.keywordsRequired'))
return return
} }
if (this.series.genre_id === undefined || this.series.genre_id === null || this.series.genre_id <= 0) { if (this.series.genre_id === undefined || this.series.genre_id === null || this.series.genre_id <= 0) {
this.notifyError('올바른 장르를 선택하세요') this.notifyError(this.$t('view.content.series.validation.genreRequired'))
return return
} }
@@ -615,10 +615,10 @@ export default {
return {name: item.genre, value: item.id} return {name: item.genre, value: item.id}
}) })
} else { } else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(res.data.message || this.$t('common.error.unknown'))
} }
} catch (e) { } catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(this.$t('common.error.unknown'))
} }
}, },

View File

@@ -2,7 +2,7 @@
<div> <div>
<v-toolbar dark> <v-toolbar dark>
<v-spacer /> <v-spacer />
<v-toolbar-title>시그니처 관리</v-toolbar-title> <v-toolbar-title>{{ $t('view.signature.title') }}</v-toolbar-title>
<v-spacer /> <v-spacer />
</v-toolbar> </v-toolbar>
<v-container> <v-container>
@@ -16,7 +16,7 @@
depressed depressed
@click="showWriteDialog" @click="showWriteDialog"
> >
시그니처 등록 {{ $t('view.signature.actions.create') }}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -34,7 +34,7 @@
for="sort-newest" for="sort-newest"
class="radio-label-sort" class="radio-label-sort"
> >
최신순 {{ $t('view.signature.sort.newest') }}
</label> </label>
</v-col> </v-col>
<v-col> <v-col>
@@ -50,7 +50,7 @@
for="sort-can-high" for="sort-can-high"
class="radio-label-sort" class="radio-label-sort"
> >
높은캔순 {{ $t('view.signature.sort.canHigh') }}
</label> </label>
</v-col> </v-col>
<v-col> <v-col>
@@ -66,7 +66,7 @@
for="sort-can-low" for="sort-can-low"
class="radio-label-sort" class="radio-label-sort"
> >
낮은캔순 {{ $t('view.signature.sort.canLow') }}
</label> </label>
</v-col> </v-col>
<v-col cols="10" /> <v-col cols="10" />
@@ -112,7 +112,7 @@
:disabled="is_loading" :disabled="is_loading"
@click="showModifyDialog(item)" @click="showModifyDialog(item)"
> >
수정 {{ $t('view.signature.actions.edit') }}
</v-btn> </v-btn>
</v-col> </v-col>
<v-col> <v-col>
@@ -120,7 +120,7 @@
:disabled="is_loading" :disabled="is_loading"
@click="showDeleteConfirm(item)" @click="showDeleteConfirm(item)"
> >
삭제 {{ $t('view.signature.actions.delete') }}
</v-btn> </v-btn>
</v-col> </v-col>
<v-col /> <v-col />
@@ -148,17 +148,17 @@
> >
<v-card> <v-card>
<v-card-title> <v-card-title>
시그니처 등록 {{ $t('view.signature.dialog.createTitle') }}
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
{{ $t('view.signature.fields.can') }}
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<v-text-field <v-text-field
v-model="can" v-model="can"
label="" :label="$t('view.signature.fields.can')"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -167,7 +167,7 @@
<v-card-text> <v-card-text>
<v-row> <v-row>
<v-col cols="4"> <v-col cols="4">
19 {{ $t('view.signature.fields.adult') }}
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<input <input
@@ -181,12 +181,12 @@
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
이미지 {{ $t('view.signature.fields.image') }}
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<div class="image-select"> <div class="image-select">
<label for="image"> <label for="image">
이미지 불러오기 {{ $t('view.signature.actions.loadImage') }}
</label> </label>
<v-file-input <v-file-input
id="image" id="image"
@@ -208,12 +208,12 @@
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
시간() {{ $t('view.signature.fields.timeSec') }}
</v-col> </v-col>
<v-col cols="8"> <v-col cols="8">
<v-text-field <v-text-field
v-model="time" v-model="time"
label="시간(초)" :label="$t('view.signature.fields.timeSec')"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -325,14 +325,14 @@
text text
@click="cancel" @click="cancel"
> >
취소 {{ $t('common.actions.cancel') }}
</v-btn> </v-btn>
<v-btn <v-btn
color="blue darken-1" color="blue darken-1"
text text
@click="modifySignatureCan" @click="modifySignatureCan"
> >
수정 {{ $t('view.signature.actions.edit') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@@ -347,7 +347,7 @@
<v-card> <v-card>
<v-card-text /> <v-card-text />
<v-card-text> <v-card-text>
삭제하시겠습니까? {{ $t('common.confirm.delete') }}
</v-card-text> </v-card-text>
<v-card-actions v-show="!is_loading"> <v-card-actions v-show="!is_loading">
<v-spacer /> <v-spacer />
@@ -356,14 +356,14 @@
text text
@click="cancel" @click="cancel"
> >
취소 {{ $t('common.actions.cancel') }}
</v-btn> </v-btn>
<v-btn <v-btn
color="blue darken-1" color="blue darken-1"
text text
@click="deleteSignature" @click="deleteSignature"
> >
확인 {{ $t('common.actions.confirm') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@@ -376,7 +376,7 @@
persistent persistent
> >
<v-card> <v-card>
<v-card-title>이미지 크롭</v-card-title> <v-card-title>{{ $t('view.signature.dialog.cropTitle') }}</v-card-title>
<v-card-text> <v-card-text>
<div class="cropper-wrapper"> <div class="cropper-wrapper">
<img <img
@@ -394,14 +394,14 @@
text text
@click="cancelCropper" @click="cancelCropper"
> >
취소 {{ $t('common.actions.cancel') }}
</v-btn> </v-btn>
<v-btn <v-btn
color="blue darken-1" color="blue darken-1"
text text
@click="cropImage" @click="cropImage"
> >
크롭 완료 {{ $t('view.signature.actions.cropDone') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@@ -444,31 +444,31 @@ export default {
headers: [ headers: [
{ {
text: '캔', text: this.$t('view.signature.headers.can'),
align: 'center', align: 'center',
sortable: false, sortable: false,
value: 'can', value: 'can',
}, },
{ {
text: '19금', text: this.$t('view.signature.headers.adult'),
align: 'center', align: 'center',
sortable: false, sortable: false,
value: 'isAdult', value: 'isAdult',
}, },
{ {
text: '이미지', text: this.$t('view.signature.headers.image'),
align: 'center', align: 'center',
sortable: false, sortable: false,
value: 'image', value: 'image',
}, },
{ {
text: '시간(초)', text: this.$t('view.signature.headers.timeSec'),
align: 'center', align: 'center',
sortable: false, sortable: false,
value: 'time', value: 'time',
}, },
{ {
text: '관리', text: this.$t('view.signature.headers.actions'),
align: 'center', align: 'center',
sortable: false, sortable: false,
value: 'management', value: 'management',
@@ -565,9 +565,9 @@ export default {
this.can === 0 || this.can === 0 ||
this.image === null this.image === null
) { ) {
this.notifyError('내용을 입력하세요') this.notifyError(this.$t('view.signature.validation.fillRequired'))
} else if (this.time < 3 || this.time > 20) { } else if (this.time < 3 || this.time > 20) {
this.notifyError('시간은 3초 이상 20초 이하를 입력하세요.') this.notifyError(this.$t('view.signature.validation.timeRange'))
} else { } else {
this.submit() this.submit()
} }
@@ -622,7 +622,7 @@ export default {
this.total_page = total_page this.total_page = total_page
} }
} catch (e) { } catch (e) {
this.notifyError("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") this.notifyError(this.$t('common.error.unknown'))
} finally { } finally {
this.is_loading = false this.is_loading = false
} }
@@ -643,16 +643,16 @@ export default {
const res = await api.createSignature(formData) const res = await api.createSignature(formData)
if (res.status === 200 && res.data.success === true) { if (res.status === 200 && res.data.success === true) {
this.cancel() this.cancel()
this.notifySuccess(res.data.message || '등록되었습니다.') this.notifySuccess(res.data.message || this.$t('view.signature.messages.created'))
this.page = 1 this.page = 1
await this.getSignatureList() await this.getSignatureList()
} else { } else {
this.is_loading = false this.is_loading = false
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(res.data.message || this.$t('common.error.unknown'))
} }
} catch (e) { } catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(this.$t('common.error.unknown'))
this.is_loading = false this.is_loading = false
} finally { } finally {
this.is_loading = false this.is_loading = false
@@ -666,7 +666,7 @@ export default {
this.can === this.selected_signature_can.can && this.can === this.selected_signature_can.can &&
this.time === this.selected_signature_can.time this.time === this.selected_signature_can.time
) { ) {
this.notifyError('변경사항이 없습니다.') this.notifyError(this.$t('view.signature.messages.noChanges'))
return; return;
} }
@@ -697,16 +697,16 @@ export default {
const res = await api.modifySignature(formData) const res = await api.modifySignature(formData)
if (res.status === 200 && res.data.success === true) { if (res.status === 200 && res.data.success === true) {
this.cancel() this.cancel()
this.notifySuccess(res.data.message || '수정되었습니다.') this.notifySuccess(res.data.message || this.$t('view.signature.messages.updated'))
this.page = 1 this.page = 1
await this.getSignatureList() await this.getSignatureList()
} else { } else {
this.is_loading = false this.is_loading = false
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(res.data.message || this.$t('common.error.unknown'))
} }
} catch (e) { } catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(this.$t('common.error.unknown'))
this.is_loading = false this.is_loading = false
} finally { } finally {
this.is_loading = false this.is_loading = false
@@ -726,16 +726,16 @@ export default {
const res = await api.modifySignature(formData) const res = await api.modifySignature(formData)
if (res.status === 200 && res.data.success === true) { if (res.status === 200 && res.data.success === true) {
this.cancel() this.cancel()
this.notifySuccess(res.data.message || '등록되었습니다.') this.notifySuccess(res.data.message || this.$t('view.signature.messages.deleted'))
this.page = 1 this.page = 1
await this.getSignatureList() await this.getSignatureList()
} else { } else {
this.is_loading = false this.is_loading = false
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(res.data.message || this.$t('common.error.unknown'))
} }
} catch (e) { } catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') this.notifyError(this.$t('common.error.unknown'))
this.is_loading = false this.is_loading = false
} finally { } finally {
this.is_loading = false this.is_loading = false