feat(i18n): 공통/주요 화면 문자열 치환, 포맷 정책 적용, 언어 전환 UX 및 스캔 스크립트 추가

This commit is contained in:
Yu Sung
2026-05-08 14:09:58 +09:00
parent c2663a1e9d
commit 43d0ebc9ed
13 changed files with 459 additions and 178 deletions

View File

@@ -9,12 +9,13 @@
- [x] 리소스 파일 생성(`src/locales/ko.json`, `src/locales/en.json`, `src/locales/ja.json`)
- [x] Vuetify 언어팩 연동(`src/plugins/vuetify.js`) 및 초기 로케일 동기화
- [x] `main.js`에서 vue-i18n ↔ Vuetify 로케일 동기화 로직 추가
- [ ] 공통 컴포넌트 문자열 치환(네비/레이아웃/다이얼로그)
- [x] 공통 컴포넌트 문자열 치환(네비/레이아웃/다이얼로그)
- [x] `src/components/SideMenu.vue` 로그아웃/에러/기본 메뉴 텍스트 치환
- [ ] 주요 뷰(`views/Agent/*`) 1차 치환
- [ ] 날짜/숫자/통화 포맷 정책 적용(ja: JPY 소수점 미사용 등)
- [ ] 하드코딩 탐지/미번역 키 점검(정규식 스캔 + missing 핸들러)
- [ ] 언어 전환 UX(드롭다운) 및 영속 저장(localStorage)
- [x] `src/App.vue` 앱바 타이틀 치환 및 언어 드롭다운 추가
- [x] 주요 뷰(`views/Agent/*`) 1차 치환
- [x] 날짜/숫자/통화 포맷 정책 적용(ja: JPY 소수점 미사용 등)
- [x] 하드코딩 탐지/미번역 키 점검(정규식 스캔 + missing 핸들러)
- [x] 언어 전환 UX(드롭다운) 및 영속 저장(localStorage)
## 키 네이밍 규칙
- 네임스페이스 기반: `common.*`, `comp.*`, `view.*`
@@ -33,3 +34,16 @@
- 실행 명령: (로컬) `npm i``npm run serve` 예정
- 결과: 아직 실행 전. 의존성 설치 및 로컬 실행 시에 확인 예정
- 보완: 언어 전환 UI/UX 및 나머지 화면 치환, 하드코딩 스캔 적용 예정
### 2차 구현 (2026-05-08)
- 무엇을: 공통 레이아웃(App 바) 및 주요 뷰(Agents/Creators, Calculate/*) 문자열 i18n 치환, 숫자/통화 포맷 적용, 언어 드롭다운 추가, 하드코딩 스캔 스크립트 추가
- 왜: 다국어 전환 시 모든 핵심 화면이 정상 동작하고, 통화/숫자 포맷(특히 JPY 무소수)이 일관되게 표시되도록 하기 위함
- 어떻게:
- 실행 명령 1: `npm run i18n:scan`
- 기대 결과: 남아있는 한/일문 하드코딩 라인 목록 출력(없다면 빈 결과). 개발 중 점검용으로 유지.
- 실행 명령 2: `npm run serve` 후 브라우저에서 App 바의 언어 드롭다운으로 `ko/en/ja` 전환
- 확인 항목: App 타이틀/사이드메뉴/툴바/테이블 헤더/합계 행 텍스트가 즉시 해당 언어로 변경됨 ✓
- 실행 명령 3: 각 정산 화면의 합계/금액 컬럼 확인
- 확인 항목: en=USD 통화기호/소수 2자리, ko=KRW 소수 0자리, ja=JPY 소수 0자리로 `$n(..., 'currency')` 표기 ✓
- 오류 핸들링: API 실패 시 공통 메시지 `common.error.unknown` 사용, 목록 실패 시 `common.error.fetchFailed` 사용 ✓
- Vuetify 동기화: 언어 전환 시 Vuetify locale이 함께 변경되는지 확인(메시지/레이블) ✓

View File

@@ -7,7 +7,8 @@
"serve": "vue-cli-service serve --port 8888",
"build": "vue-cli-service build",
"build_development": "vue-cli-service build --mode development",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"i18n:scan": "grep -RIn --include='*.vue' --include='*.js' -E '[가-힣ぁ-んァ-ン一-龯]' src || true"
},
"dependencies": {
"core-js": "^3.6.5",

View File

@@ -7,8 +7,19 @@
dark
>
<v-spacer />
<v-toolbar-title>보이스온 크리에이터 관리자</v-toolbar-title>
<v-toolbar-title>{{ $t('common.app.title') }}</v-toolbar-title>
<v-spacer />
<v-select
v-model="selectedLocale"
class="ma-0 mr-4"
dense
hide-details
outlined
:items="localeItems"
item-text="text"
item-value="value"
style="max-width: 150px"
/>
</v-app-bar>
<v-main>
@@ -17,6 +28,36 @@
</v-app>
</template>
<script>
export default {
name: 'AppRoot',
data() {
return {
selectedLocale: null
}
},
computed: {
localeItems() {
return [
{ value: 'ko', text: this.$t('common.lang.ko') },
{ value: 'en', text: this.$t('common.lang.en') },
{ value: 'ja', text: this.$t('common.lang.ja') }
]
}
},
watch: {
selectedLocale(v) {
if (!v) return
this.$i18n.locale = v
try { localStorage.setItem('locale', v) } catch (e) { /* ignore */ }
}
},
created() {
this.selectedLocale = this.$i18n && this.$i18n.locale ? this.$i18n.locale : 'ko'
}
}
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;

View File

@@ -31,6 +31,69 @@ const i18n = new VueI18n({
locale: detectLocale(),
fallbackLocale: 'en',
messages: { ko, en, ja },
// 숫자/통화 포맷 정책
numberFormats: {
en: {
decimal: {
style: 'decimal',
minimumFractionDigits: 0,
maximumFractionDigits: 2
},
currency: {
style: 'currency',
currency: 'USD',
currencyDisplay: 'symbol',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}
},
ko: {
decimal: {
style: 'decimal',
minimumFractionDigits: 0,
maximumFractionDigits: 2
},
currency: {
style: 'currency',
currency: 'KRW',
currencyDisplay: 'symbol',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}
},
ja: {
decimal: {
style: 'decimal',
minimumFractionDigits: 0,
maximumFractionDigits: 2
},
currency: {
style: 'currency',
currency: 'JPY',
currencyDisplay: 'symbol',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}
}
},
// 날짜/시간 포맷(예시)
dateTimeFormats: {
en: {
short: {
year: 'numeric', month: '2-digit', day: '2-digit'
}
},
ko: {
short: {
year: 'numeric', month: '2-digit', day: '2-digit'
}
},
ja: {
short: {
year: 'numeric', month: '2-digit', day: '2-digit'
}
}
},
silentTranslationWarn: process.env.NODE_ENV === 'production',
missing(locale, key) {
if (process.env.NODE_ENV !== 'production') {

View File

@@ -2,7 +2,19 @@
"common": {
"logout": "Log out",
"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."
},
"unit": {
"can": "CAN"
},
"lang": {
"ko": "한국어",
"en": "English",
"ja": "日本語"
},
"app": {
"title": "VoiceON Creator Admin"
}
},
"comp": {
@@ -16,5 +28,37 @@
"channelDonation": "Settlement by channel donations"
}
}
},
"view": {
"agent": {
"creators": {
"title": "Affiliated creators",
"titleWithCount": "Affiliated creators - {count}",
"headers": {
"no": "No.",
"profile": "Profile",
"nickname": "Nickname"
}
},
"calculate": {
"common": {
"startDate": "Start date",
"endDate": "End date",
"search": "Search",
"total": "Total"
},
"columns": {
"nickname": "Nickname",
"count": "Count",
"totalCan": "Total CAN",
"krw": "KRW",
"fee": "Fee",
"settlementAmount": "Settlement amount",
"tax": "Tax",
"depositAmount": "Deposit amount",
"agentSettlementAmount": "Agent settlement amount"
}
}
}
}
}

View File

@@ -2,7 +2,19 @@
"common": {
"logout": "ログアウト",
"error": {
"unknown": "不明なエラーが発生しました。再度ログインしてください。"
"unknown": "不明なエラーが発生しました。再度ログインしてください。",
"fetchFailed": "リストを読み込めませんでした。もう一度お試しください。"
},
"unit": {
"can": "CAN"
},
"lang": {
"ko": "한국어",
"en": "English",
"ja": "日本語"
},
"app": {
"title": "ボイスオン クリエイター管理"
}
},
"comp": {
@@ -16,5 +28,37 @@
"channelDonation": "クリエイター別チャンネル支援精算"
}
}
},
"view": {
"agent": {
"creators": {
"title": "所属クリエイター",
"titleWithCount": "所属クリエイター - {count}名",
"headers": {
"no": "番号",
"profile": "プロフィール",
"nickname": "ニックネーム"
}
},
"calculate": {
"common": {
"startDate": "開始日",
"endDate": "終了日",
"search": "検索",
"total": "合計"
},
"columns": {
"nickname": "ニックネーム",
"count": "件数",
"totalCan": "合計 CAN",
"krw": "ウォン",
"fee": "手数料",
"settlementAmount": "精算金額",
"tax": "税金",
"depositAmount": "入金額",
"agentSettlementAmount": "エージェント精算金額"
}
}
}
}
}

View File

@@ -2,7 +2,19 @@
"common": {
"logout": "로그아웃",
"error": {
"unknown": "알 수 없는 오류가 발생했습니다. 다시 로그인 해주세요!"
"unknown": "알 수 없는 오류가 발생했습니다. 다시 로그인 해주세요!",
"fetchFailed": "목록을 불러오지 못했습니다. 다시 시도해 주세요."
},
"unit": {
"can": "캔"
},
"lang": {
"ko": "한국어",
"en": "English",
"ja": "日本語"
},
"app": {
"title": "보이스온 크리에이터 관리자"
}
},
"comp": {
@@ -16,5 +28,37 @@
"channelDonation": "크리에이터별 채널 후원 정산"
}
}
},
"view": {
"agent": {
"creators": {
"title": "소속 크리에이터",
"titleWithCount": "소속 크리에이터 - {count}명",
"headers": {
"no": "순번",
"profile": "프로필",
"nickname": "닉네임"
}
},
"calculate": {
"common": {
"startDate": "시작일",
"endDate": "종료일",
"search": "조회",
"total": "합계"
},
"columns": {
"nickname": "닉네임",
"count": "건수",
"totalCan": "총 CAN",
"krw": "원화",
"fee": "수수료",
"settlementAmount": "정산금액",
"tax": "세금",
"depositAmount": "입금액",
"agentSettlementAmount": "에이전트 정산금액"
}
}
}
}
}

View File

@@ -2,7 +2,7 @@
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>크리에이터별 채널 후원 정산</v-toolbar-title>
<v-toolbar-title>{{ $t('comp.sideMenu.calc.channelDonation') }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
@@ -29,7 +29,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="시작일"
:label="$t('view.agent.calculate.common.startDate')"
readonly
dense
:value="start_date"
@@ -58,7 +58,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="종료일"
:label="$t('view.agent.calculate.common.endDate')"
readonly
dense
:value="end_date"
@@ -82,7 +82,7 @@
:loading="is_loading"
@click="fetchItems"
>
조회
{{ $t('view.agent.calculate.common.search') }}
</v-btn>
</v-col>
</v-row>
@@ -99,38 +99,38 @@
>
<template slot="body.prepend">
<tr>
<td>합계</td>
<td>{{ (total.count || 0).toLocaleString() }}</td>
<td>{{ (total.totalCan || 0).toLocaleString() }} </td>
<td>{{ (total.krw || 0).toLocaleString() }} </td>
<td>{{ (total.fee || 0).toLocaleString() }} </td>
<td>{{ (total.settlementAmount || 0).toLocaleString() }} </td>
<td>{{ (total.tax || 0).toLocaleString() }} </td>
<td>{{ (total.depositAmount || 0).toLocaleString() }} </td>
<td>{{ (total.agentSettlementAmount || 0).toLocaleString() }} </td>
<td>{{ $t('view.agent.calculate.common.total') }}</td>
<td>{{ $n(total.count || 0, 'decimal') }}</td>
<td>{{ $n(total.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}</td>
<td>{{ $n(total.krw || 0, 'currency') }}</td>
<td>{{ $n(total.fee || 0, 'currency') }}</td>
<td>{{ $n(total.settlementAmount || 0, 'currency') }}</td>
<td>{{ $n(total.tax || 0, 'currency') }}</td>
<td>{{ $n(total.depositAmount || 0, 'currency') }}</td>
<td>{{ $n(total.agentSettlementAmount || 0, 'currency') }}</td>
</tr>
</template>
<template v-slot:item.totalCan="{ item }">
{{ (item.totalCan || 0).toLocaleString() }}
{{ $n(item.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}
</template>
<template v-slot:item.krw="{ item }">
{{ (item.krw || 0).toLocaleString() }}
{{ $n(item.krw || 0, 'currency') }}
</template>
<template v-slot:item.fee="{ item }">
{{ (item.fee || 0).toLocaleString() }}
{{ $n(item.fee || 0, 'currency') }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ (item.settlementAmount || 0).toLocaleString() }}
{{ $n(item.settlementAmount || 0, 'currency') }}
</template>
<template v-slot:item.tax="{ item }">
{{ (item.tax || 0).toLocaleString() }}
{{ $n(item.tax || 0, 'currency') }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ (item.depositAmount || 0).toLocaleString() }}
{{ $n(item.depositAmount || 0, 'currency') }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ (item.agentSettlementAmount || 0).toLocaleString() }}
{{ $n(item.agentSettlementAmount || 0, 'currency') }}
</template>
</v-data-table>
</v-col>
@@ -177,16 +177,21 @@ export default {
depositAmount: 0,
agentSettlementAmount: 0
},
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center', sortable: false },
{ text: '건수', value: 'count', align: 'center', sortable: false },
{ text: '총 CAN', value: 'totalCan', align: 'center', sortable: false },
{ text: '원화', value: 'krw', align: 'center', sortable: false },
{ text: '수수료', value: 'fee', align: 'center', sortable: false },
{ text: '정산금액', value: 'settlementAmount', align: 'center', sortable: false },
{ text: '세금', value: 'tax', align: 'center', sortable: false },
{ text: '입금액', value: 'depositAmount', align: 'center', sortable: false },
{ text: '에이전트 정산금액', value: 'agentSettlementAmount', align: 'center', sortable: false },
// headers는 locale 변경 시 computed에서 생성
}
},
computed: {
headers() {
return [
{ text: this.$t('view.agent.calculate.columns.nickname'), value: 'creatorNickname', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.count'), value: 'count', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.totalCan'), value: 'totalCan', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.krw'), value: 'krw', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.fee'), value: 'fee', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.settlementAmount'), value: 'settlementAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.tax'), value: 'tax', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.depositAmount'), value: 'depositAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.agentSettlementAmount'), value: 'agentSettlementAmount', align: 'center', sortable: false }
]
}
},
@@ -227,10 +232,10 @@ export default {
const totalPage = Math.ceil((data.totalCount || 0) / this.page_size)
this.total_page = totalPage > 0 ? totalPage : 1
} else {
this.notifyError(res.data?.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(res.data?.message || this.$t('common.error.unknown'))
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(this.$t('common.error.unknown'))
} finally {
this.is_loading = false
}

View File

@@ -2,7 +2,7 @@
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>크리에이터별 커뮤니티 정산</v-toolbar-title>
<v-toolbar-title>{{ $t('comp.sideMenu.calc.community') }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
@@ -29,7 +29,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="시작일"
:label="$t('view.agent.calculate.common.startDate')"
readonly
dense
:value="start_date"
@@ -58,7 +58,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="종료일"
:label="$t('view.agent.calculate.common.endDate')"
readonly
dense
:value="end_date"
@@ -82,7 +82,7 @@
:loading="is_loading"
@click="fetchItems"
>
조회
{{ $t('view.agent.calculate.common.search') }}
</v-btn>
</v-col>
</v-row>
@@ -99,38 +99,38 @@
>
<template slot="body.prepend">
<tr>
<td>합계</td>
<td>{{ (total.count || 0).toLocaleString() }}</td>
<td>{{ (total.totalCan || 0).toLocaleString() }} </td>
<td>{{ (total.krw || 0).toLocaleString() }} </td>
<td>{{ (total.fee || 0).toLocaleString() }} </td>
<td>{{ (total.settlementAmount || 0).toLocaleString() }} </td>
<td>{{ (total.tax || 0).toLocaleString() }} </td>
<td>{{ (total.depositAmount || 0).toLocaleString() }} </td>
<td>{{ (total.agentSettlementAmount || 0).toLocaleString() }} </td>
<td>{{ $t('view.agent.calculate.common.total') }}</td>
<td>{{ $n(total.count || 0, 'decimal') }}</td>
<td>{{ $n(total.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}</td>
<td>{{ $n(total.krw || 0, 'currency') }}</td>
<td>{{ $n(total.fee || 0, 'currency') }}</td>
<td>{{ $n(total.settlementAmount || 0, 'currency') }}</td>
<td>{{ $n(total.tax || 0, 'currency') }}</td>
<td>{{ $n(total.depositAmount || 0, 'currency') }}</td>
<td>{{ $n(total.agentSettlementAmount || 0, 'currency') }}</td>
</tr>
</template>
<template v-slot:item.totalCan="{ item }">
{{ (item.totalCan || 0).toLocaleString() }}
{{ $n(item.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}
</template>
<template v-slot:item.krw="{ item }">
{{ (item.krw || 0).toLocaleString() }}
{{ $n(item.krw || 0, 'currency') }}
</template>
<template v-slot:item.fee="{ item }">
{{ (item.fee || 0).toLocaleString() }}
{{ $n(item.fee || 0, 'currency') }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ (item.settlementAmount || 0).toLocaleString() }}
{{ $n(item.settlementAmount || 0, 'currency') }}
</template>
<template v-slot:item.tax="{ item }">
{{ (item.tax || 0).toLocaleString() }}
{{ $n(item.tax || 0, 'currency') }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ (item.depositAmount || 0).toLocaleString() }}
{{ $n(item.depositAmount || 0, 'currency') }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ (item.agentSettlementAmount || 0).toLocaleString() }}
{{ $n(item.agentSettlementAmount || 0, 'currency') }}
</template>
</v-data-table>
</v-col>
@@ -177,16 +177,21 @@ export default {
depositAmount: 0,
agentSettlementAmount: 0
},
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center', sortable: false },
{ text: '건수', value: 'count', align: 'center', sortable: false },
{ text: '총 CAN', value: 'totalCan', align: 'center', sortable: false },
{ text: '원화', value: 'krw', align: 'center', sortable: false },
{ text: '수수료', value: 'fee', align: 'center', sortable: false },
{ text: '정산금액', value: 'settlementAmount', align: 'center', sortable: false },
{ text: '세금', value: 'tax', align: 'center', sortable: false },
{ text: '입금액', value: 'depositAmount', align: 'center', sortable: false },
{ text: '에이전트 정산금액', value: 'agentSettlementAmount', align: 'center', sortable: false },
// headers는 locale 변경 시 computed에서 생성
}
},
computed: {
headers() {
return [
{ text: this.$t('view.agent.calculate.columns.nickname'), value: 'creatorNickname', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.count'), value: 'count', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.totalCan'), value: 'totalCan', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.krw'), value: 'krw', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.fee'), value: 'fee', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.settlementAmount'), value: 'settlementAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.tax'), value: 'tax', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.depositAmount'), value: 'depositAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.agentSettlementAmount'), value: 'agentSettlementAmount', align: 'center', sortable: false }
]
}
},
@@ -227,10 +232,10 @@ export default {
const totalPage = Math.ceil((data.totalCount || 0) / this.page_size)
this.total_page = totalPage > 0 ? totalPage : 1
} else {
this.notifyError(res.data?.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(res.data?.message || this.$t('common.error.unknown'))
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(this.$t('common.error.unknown'))
} finally {
this.is_loading = false
}

View File

@@ -2,7 +2,7 @@
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>크리에이터별 콘텐츠 정산</v-toolbar-title>
<v-toolbar-title>{{ $t('comp.sideMenu.calc.content') }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
@@ -29,7 +29,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="시작일"
:label="$t('view.agent.calculate.common.startDate')"
readonly
dense
:value="start_date"
@@ -58,7 +58,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="종료일"
:label="$t('view.agent.calculate.common.endDate')"
readonly
dense
:value="end_date"
@@ -82,7 +82,7 @@
:loading="is_loading"
@click="fetchItems"
>
조회
{{ $t('view.agent.calculate.common.search') }}
</v-btn>
</v-col>
</v-row>
@@ -99,38 +99,38 @@
>
<template slot="body.prepend">
<tr>
<td>합계</td>
<td>{{ (total.count || 0).toLocaleString() }}</td>
<td>{{ (total.totalCan || 0).toLocaleString() }} </td>
<td>{{ (total.krw || 0).toLocaleString() }} </td>
<td>{{ (total.fee || 0).toLocaleString() }} </td>
<td>{{ (total.settlementAmount || 0).toLocaleString() }} </td>
<td>{{ (total.tax || 0).toLocaleString() }} </td>
<td>{{ (total.depositAmount || 0).toLocaleString() }} </td>
<td>{{ (total.agentSettlementAmount || 0).toLocaleString() }} </td>
<td>{{ $t('view.agent.calculate.common.total') }}</td>
<td>{{ $n(total.count || 0, 'decimal') }}</td>
<td>{{ $n(total.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}</td>
<td>{{ $n(total.krw || 0, 'currency') }}</td>
<td>{{ $n(total.fee || 0, 'currency') }}</td>
<td>{{ $n(total.settlementAmount || 0, 'currency') }}</td>
<td>{{ $n(total.tax || 0, 'currency') }}</td>
<td>{{ $n(total.depositAmount || 0, 'currency') }}</td>
<td>{{ $n(total.agentSettlementAmount || 0, 'currency') }}</td>
</tr>
</template>
<template v-slot:item.totalCan="{ item }">
{{ (item.totalCan || 0).toLocaleString() }}
{{ $n(item.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}
</template>
<template v-slot:item.krw="{ item }">
{{ (item.krw || 0).toLocaleString() }}
{{ $n(item.krw || 0, 'currency') }}
</template>
<template v-slot:item.fee="{ item }">
{{ (item.fee || 0).toLocaleString() }}
{{ $n(item.fee || 0, 'currency') }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ (item.settlementAmount || 0).toLocaleString() }}
{{ $n(item.settlementAmount || 0, 'currency') }}
</template>
<template v-slot:item.tax="{ item }">
{{ (item.tax || 0).toLocaleString() }}
{{ $n(item.tax || 0, 'currency') }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ (item.depositAmount || 0).toLocaleString() }}
{{ $n(item.depositAmount || 0, 'currency') }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ (item.agentSettlementAmount || 0).toLocaleString() }}
{{ $n(item.agentSettlementAmount || 0, 'currency') }}
</template>
</v-data-table>
</v-col>
@@ -177,16 +177,21 @@ export default {
depositAmount: 0,
agentSettlementAmount: 0
},
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center', sortable: false },
{ text: '건수', value: 'count', align: 'center', sortable: false },
{ text: '총 CAN', value: 'totalCan', align: 'center', sortable: false },
{ text: '원화', value: 'krw', align: 'center', sortable: false },
{ text: '수수료', value: 'fee', align: 'center', sortable: false },
{ text: '정산금액', value: 'settlementAmount', align: 'center', sortable: false },
{ text: '세금', value: 'tax', align: 'center', sortable: false },
{ text: '입금액', value: 'depositAmount', align: 'center', sortable: false },
{ text: '에이전트 정산금액', value: 'agentSettlementAmount', align: 'center', sortable: false },
// headers는 locale 변경 시 computed에서 생성
}
},
computed: {
headers() {
return [
{ text: this.$t('view.agent.calculate.columns.nickname'), value: 'creatorNickname', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.count'), value: 'count', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.totalCan'), value: 'totalCan', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.krw'), value: 'krw', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.fee'), value: 'fee', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.settlementAmount'), value: 'settlementAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.tax'), value: 'tax', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.depositAmount'), value: 'depositAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.agentSettlementAmount'), value: 'agentSettlementAmount', align: 'center', sortable: false }
]
}
},
@@ -227,10 +232,10 @@ export default {
const totalPage = Math.ceil((data.totalCount || 0) / this.page_size)
this.total_page = totalPage > 0 ? totalPage : 1
} else {
this.notifyError(res.data?.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(res.data?.message || this.$t('common.error.unknown'))
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(this.$t('common.error.unknown'))
} finally {
this.is_loading = false
}

View File

@@ -2,7 +2,7 @@
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>크리에이터별 콘텐츠 후원 정산</v-toolbar-title>
<v-toolbar-title>{{ $t('comp.sideMenu.calc.contentDonation') }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
@@ -29,7 +29,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="시작일"
:label="$t('view.agent.calculate.common.startDate')"
readonly
dense
:value="start_date"
@@ -58,7 +58,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="종료일"
:label="$t('view.agent.calculate.common.endDate')"
readonly
dense
:value="end_date"
@@ -82,7 +82,7 @@
:loading="is_loading"
@click="fetchItems"
>
조회
{{ $t('view.agent.calculate.common.search') }}
</v-btn>
</v-col>
</v-row>
@@ -99,38 +99,38 @@
>
<template slot="body.prepend">
<tr>
<td>합계</td>
<td>{{ (total.count || 0).toLocaleString() }}</td>
<td>{{ (total.totalCan || 0).toLocaleString() }} </td>
<td>{{ (total.krw || 0).toLocaleString() }} </td>
<td>{{ (total.fee || 0).toLocaleString() }} </td>
<td>{{ (total.settlementAmount || 0).toLocaleString() }} </td>
<td>{{ (total.tax || 0).toLocaleString() }} </td>
<td>{{ (total.depositAmount || 0).toLocaleString() }} </td>
<td>{{ (total.agentSettlementAmount || 0).toLocaleString() }} </td>
<td>{{ $t('view.agent.calculate.common.total') }}</td>
<td>{{ $n(total.count || 0, 'decimal') }}</td>
<td>{{ $n(total.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}</td>
<td>{{ $n(total.krw || 0, 'currency') }}</td>
<td>{{ $n(total.fee || 0, 'currency') }}</td>
<td>{{ $n(total.settlementAmount || 0, 'currency') }}</td>
<td>{{ $n(total.tax || 0, 'currency') }}</td>
<td>{{ $n(total.depositAmount || 0, 'currency') }}</td>
<td>{{ $n(total.agentSettlementAmount || 0, 'currency') }}</td>
</tr>
</template>
<template v-slot:item.totalCan="{ item }">
{{ (item.totalCan || 0).toLocaleString() }}
{{ $n(item.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}
</template>
<template v-slot:item.krw="{ item }">
{{ (item.krw || 0).toLocaleString() }}
{{ $n(item.krw || 0, 'currency') }}
</template>
<template v-slot:item.fee="{ item }">
{{ (item.fee || 0).toLocaleString() }}
{{ $n(item.fee || 0, 'currency') }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ (item.settlementAmount || 0).toLocaleString() }}
{{ $n(item.settlementAmount || 0, 'currency') }}
</template>
<template v-slot:item.tax="{ item }">
{{ (item.tax || 0).toLocaleString() }}
{{ $n(item.tax || 0, 'currency') }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ (item.depositAmount || 0).toLocaleString() }}
{{ $n(item.depositAmount || 0, 'currency') }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ (item.agentSettlementAmount || 0).toLocaleString() }}
{{ $n(item.agentSettlementAmount || 0, 'currency') }}
</template>
</v-data-table>
</v-col>
@@ -177,16 +177,21 @@ export default {
depositAmount: 0,
agentSettlementAmount: 0
},
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center', sortable: false },
{ text: '건수', value: 'count', align: 'center', sortable: false },
{ text: '총 CAN', value: 'totalCan', align: 'center', sortable: false },
{ text: '원화', value: 'krw', align: 'center', sortable: false },
{ text: '수수료', value: 'fee', align: 'center', sortable: false },
{ text: '정산금액', value: 'settlementAmount', align: 'center', sortable: false },
{ text: '세금', value: 'tax', align: 'center', sortable: false },
{ text: '입금액', value: 'depositAmount', align: 'center', sortable: false },
{ text: '에이전트 정산금액', value: 'agentSettlementAmount', align: 'center', sortable: false },
// headers는 locale 변경 시 computed에서 생성
}
},
computed: {
headers() {
return [
{ text: this.$t('view.agent.calculate.columns.nickname'), value: 'creatorNickname', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.count'), value: 'count', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.totalCan'), value: 'totalCan', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.krw'), value: 'krw', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.fee'), value: 'fee', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.settlementAmount'), value: 'settlementAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.tax'), value: 'tax', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.depositAmount'), value: 'depositAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.agentSettlementAmount'), value: 'agentSettlementAmount', align: 'center', sortable: false }
]
}
},
@@ -227,10 +232,10 @@ export default {
const totalPage = Math.ceil((data.totalCount || 0) / this.page_size)
this.total_page = totalPage > 0 ? totalPage : 1
} else {
this.notifyError(res.data?.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(res.data?.message || this.$t('common.error.unknown'))
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(this.$t('common.error.unknown'))
} finally {
this.is_loading = false
}

View File

@@ -2,7 +2,7 @@
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>크리에이터별 라이브 정산</v-toolbar-title>
<v-toolbar-title>{{ $t('comp.sideMenu.calc.live') }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
@@ -29,7 +29,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="시작일"
:label="$t('view.agent.calculate.common.startDate')"
readonly
dense
:value="start_date"
@@ -58,7 +58,7 @@
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
label="종료일"
:label="$t('view.agent.calculate.common.endDate')"
readonly
dense
:value="end_date"
@@ -82,7 +82,7 @@
:loading="is_loading"
@click="fetchItems"
>
조회
{{ $t('view.agent.calculate.common.search') }}
</v-btn>
</v-col>
</v-row>
@@ -99,38 +99,38 @@
>
<template slot="body.prepend">
<tr>
<td>합계</td>
<td>{{ (total.count || 0).toLocaleString() }}</td>
<td>{{ (total.totalCan || 0).toLocaleString() }} </td>
<td>{{ (total.krw || 0).toLocaleString() }} </td>
<td>{{ (total.fee || 0).toLocaleString() }} </td>
<td>{{ (total.settlementAmount || 0).toLocaleString() }} </td>
<td>{{ (total.tax || 0).toLocaleString() }} </td>
<td>{{ (total.depositAmount || 0).toLocaleString() }} </td>
<td>{{ (total.agentSettlementAmount || 0).toLocaleString() }} </td>
<td>{{ $t('view.agent.calculate.common.total') }}</td>
<td>{{ $n(total.count || 0, 'decimal') }}</td>
<td>{{ $n(total.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}</td>
<td>{{ $n(total.krw || 0, 'currency') }}</td>
<td>{{ $n(total.fee || 0, 'currency') }}</td>
<td>{{ $n(total.settlementAmount || 0, 'currency') }}</td>
<td>{{ $n(total.tax || 0, 'currency') }}</td>
<td>{{ $n(total.depositAmount || 0, 'currency') }}</td>
<td>{{ $n(total.agentSettlementAmount || 0, 'currency') }}</td>
</tr>
</template>
<template v-slot:item.totalCan="{ item }">
{{ (item.totalCan || 0).toLocaleString() }}
{{ $n(item.totalCan || 0, 'decimal') }} {{ $t('common.unit.can') }}
</template>
<template v-slot:item.krw="{ item }">
{{ (item.krw || 0).toLocaleString() }}
{{ $n(item.krw || 0, 'currency') }}
</template>
<template v-slot:item.fee="{ item }">
{{ (item.fee || 0).toLocaleString() }}
{{ $n(item.fee || 0, 'currency') }}
</template>
<template v-slot:item.settlementAmount="{ item }">
{{ (item.settlementAmount || 0).toLocaleString() }}
{{ $n(item.settlementAmount || 0, 'currency') }}
</template>
<template v-slot:item.tax="{ item }">
{{ (item.tax || 0).toLocaleString() }}
{{ $n(item.tax || 0, 'currency') }}
</template>
<template v-slot:item.depositAmount="{ item }">
{{ (item.depositAmount || 0).toLocaleString() }}
{{ $n(item.depositAmount || 0, 'currency') }}
</template>
<template v-slot:item.agentSettlementAmount="{ item }">
{{ (item.agentSettlementAmount || 0).toLocaleString() }}
{{ $n(item.agentSettlementAmount || 0, 'currency') }}
</template>
</v-data-table>
</v-col>
@@ -177,16 +177,21 @@ export default {
depositAmount: 0,
agentSettlementAmount: 0
},
headers: [
{ text: '닉네임', value: 'creatorNickname', align: 'center', sortable: false },
{ text: '건수', value: 'count', align: 'center', sortable: false },
{ text: '총 CAN', value: 'totalCan', align: 'center', sortable: false },
{ text: '원화', value: 'krw', align: 'center', sortable: false },
{ text: '수수료', value: 'fee', align: 'center', sortable: false },
{ text: '정산금액', value: 'settlementAmount', align: 'center', sortable: false },
{ text: '세금', value: 'tax', align: 'center', sortable: false },
{ text: '입금액', value: 'depositAmount', align: 'center', sortable: false },
{ text: '에이전트 정산금액', value: 'agentSettlementAmount', align: 'center', sortable: false },
// headers는 locale 변경 시 computed에서 생성
}
},
computed: {
headers() {
return [
{ text: this.$t('view.agent.calculate.columns.nickname'), value: 'creatorNickname', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.count'), value: 'count', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.totalCan'), value: 'totalCan', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.krw'), value: 'krw', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.fee'), value: 'fee', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.settlementAmount'), value: 'settlementAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.tax'), value: 'tax', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.depositAmount'), value: 'depositAmount', align: 'center', sortable: false },
{ text: this.$t('view.agent.calculate.columns.agentSettlementAmount'), value: 'agentSettlementAmount', align: 'center', sortable: false }
]
}
},
@@ -227,10 +232,10 @@ export default {
const totalPage = Math.ceil((data.totalCount || 0) / this.page_size)
this.total_page = totalPage > 0 ? totalPage : 1
} else {
this.notifyError(res.data?.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(res.data?.message || this.$t('common.error.unknown'))
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.notifyError(this.$t('common.error.unknown'))
} finally {
this.is_loading = false
}

View File

@@ -2,7 +2,7 @@
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>소속 크리에이터 - {{ totalCount }}</v-toolbar-title>
<v-toolbar-title>{{ $t('view.agent.creators.titleWithCount', { count: $n(totalCount, 'decimal') }) }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
@@ -68,11 +68,7 @@ export default {
name: 'AgentCreators',
data() {
return {
headers: [
{ text: '순번', value: 'no', align: 'center', sortable: false },
{ text: '프로필', value: 'profileImageUrl', align: 'center', sortable: false },
{ text: '닉네임', value: 'creatorNickname', align: 'center' }
],
// headers는 locale 변경 시 동적으로 계산(computed headers)으로 대체
items: [],
totalCount: 0,
page: 1,
@@ -81,6 +77,15 @@ export default {
is_loading: false
};
},
computed: {
headers() {
return [
{ text: this.$t('view.agent.creators.headers.no'), value: 'no', align: 'center', sortable: false },
{ text: this.$t('view.agent.creators.headers.profile'), value: 'profileImageUrl', align: 'center', sortable: false },
{ text: this.$t('view.agent.creators.headers.nickname'), value: 'creatorNickname', align: 'center' }
]
}
},
watch: {
itemsPerPage() {
// 페이지 크기 변경 시 첫 페이지부터 다시 조회
@@ -122,7 +127,7 @@ export default {
}
if (!payload) {
this.notifyError(res?.data?.message || '목록을 불러오지 못했습니다. 다시 시도해 주세요.');
this.notifyError(res?.data?.message || this.$t('common.error.fetchFailed'));
this.items = [];
this.totalCount = 0;
this.total_page = 1;
@@ -144,7 +149,7 @@ export default {
// 최소한의 에러 로깅
// eslint-disable-next-line no-console
console.error('[AgentCreators] 목록 조회 실패', e);
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.');
this.notifyError(this.$t('common.error.unknown'));
} finally {
this.is_loading = false;
}