diff --git a/docs/20260508_국제화도입.md b/docs/20260508_국제화도입.md index 46d41db..b06509b 100644 --- a/docs/20260508_국제화도입.md +++ b/docs/20260508_국제화도입.md @@ -31,6 +31,7 @@ - [ ] P4: 공통 남은 텍스트 및 주석 정리(사용자 노출 X 주석은 후순위) - [ ] 팝업 다이얼로그 사용자 노출 텍스트 전수 치환(i18n) - [x] 시그니처 관리 페이지(`views/Signature/SignatureManagement.vue`) 치환 + - [x] 브라우저 문서 타이틀 i18n 동기화(라우터 afterEach + locale 변경 watch) ## 키 네이밍 규칙 - 네임스페이스 기반: `common.*`, `comp.*`, `view.*` @@ -109,6 +110,23 @@ - `src/store/accountStore.js`의 사용자 노출 에러 메시지 i18n 치환 - P2 (업무 핵심): 레거시 정산 화면 `views/Calculate/*` - 테이블 헤더/합계/필터 라벨/버튼 등 일괄 키 도출 → `view.calculate.*` 네임스페이스 제안 + +### 4차 수정 – 브라우저 타이틀 국제화 (2026-05-08) +- 무엇을: 라우트 이동 및 언어 전환 시 `document.title`이 선택된 언어로 표시되도록 동기화 구현(키: `common.app.title`) +- 왜: i18n 전환이 뷰 내부 텍스트에는 적용되지만 브라우저 탭 타이틀은 자동 반영되지 않기 때문 +- 어떻게: + - 코드 변경 1: `src/router/index.js` + - `import i18n from '@/i18n'` 추가 + - `router.afterEach` 훅에서 `to.matched`의 가장 깊은 라우트에서 `meta.titleKey`를 찾아 `document.title = "<번역> - i18n.t('common.app.title')"`로 설정, 없으면 `i18n.t('common.app.title')` 기본값 사용 + - 시그니처 화면 라우트에 `meta: { titleKey: 'view.signature.title' }` 추가 + - 코드 변경 2: `src/main.js` + - 앱 초기 구동 시 index.html의 하드코딩 타이틀을 `i18n.t('common.app.title')`로 1회 교체 + - `i18n.vm.$watch('locale', ...)` 내에서 현재 라우트의 가장 깊은 `meta.titleKey`를 기준으로 `document.title` 재계산, 없으면 `i18n.t('common.app.title')` 사용 + - 검증: + - 실행: `npm run serve` + - 브라우저에서 `/signature` 진입 → 탭 타이틀이 `시그니처 관리 - <앱명(언어별: common.app.title)>`로 표시됨 ✓ + - App의 언어 드롭다운으로 `en/ja` 전환 → 탭 타이틀이 해당 언어로 즉시 갱신됨 ✓ + - `meta.titleKey`가 없는 라우트 이동 시 탭 타이틀이 `i18n.t('common.app.title')`로 유지됨 ✓ - P3 (업무 빈도 높음): 콘텐츠 관리 `views/Content/*` - 목록/상세/시리즈 화면의 라벨/버튼/알림 메시지 치환 → `view.content.*` 네임스페이스 제안 - P4: 공통 남은 텍스트 및 주석 정리(사용자 노출 X 주석은 후순위) diff --git a/src/main.js b/src/main.js index 9eab205..5ac30c2 100644 --- a/src/main.js +++ b/src/main.js @@ -16,6 +16,14 @@ Vue.use(VuetifyDialog, { } }) +// 초기 진입 시 index.html의 하드코딩 타이틀을 i18n의 common.app.title로 교체 +try { + const appTitle = i18n && typeof i18n.t === 'function' ? i18n.t('common.app.title') : 'Soda Admin' + if (appTitle) document.title = `${appTitle}` +} catch (e) { + // ignore +} + // Vuetify 언어와 vue-i18n 로케일 동기화 try { vuetify.framework.lang.current = i18n.locale @@ -23,6 +31,19 @@ try { i18n.vm.$watch('locale', (val) => { vuetify.framework.lang.current = val try { localStorage.setItem('locale', val) } catch (e) { /* ignore */ } + + // 언어 변경 시 현재 라우트 기준으로 문서 타이틀 재계산 + try { + const to = router.currentRoute + const matched = (to && to.matched) ? to.matched : [] + const deepest = matched.length ? matched[matched.length - 1] : to + const key = deepest && deepest.meta && deepest.meta.titleKey + const appTitle = i18n && typeof i18n.t === 'function' ? i18n.t('common.app.title') : 'Soda Admin' + const localized = key ? i18n.t(key) : '' + document.title = key ? `${localized} - ${appTitle}` : `${appTitle}` + } catch (e) { + // ignore + } }) } } catch (e) { diff --git a/src/router/index.js b/src/router/index.js index 1fa7b8e..8bf43b3 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,6 +1,7 @@ import Vue from 'vue' import VueRouter from 'vue-router' import store from '@/store'; +import i18n from '@/i18n' import DefaultLayout from '@/layouts/default' @@ -99,7 +100,8 @@ const routes = [ { path: '/signature', name: 'SignatureManagement', - component: () => import(/* webpackChunkName: "signature" */ '../views/Signature/SignatureManagement.vue') + component: () => import(/* webpackChunkName: "signature" */ '../views/Signature/SignatureManagement.vue'), + meta: { titleKey: 'view.signature.title' } } ] }, @@ -135,4 +137,19 @@ router.beforeEach((to, from, next) => { } }) +// 라우트 변경 시 문서 타이틀을 i18n으로 갱신 +router.afterEach((to) => { + try { + // 가장 깊은 매칭 라우트에서 titleKey 탐색 + const matched = (to && to.matched) ? to.matched : [] + const deepest = matched.length ? matched[matched.length - 1] : to + const key = deepest && deepest.meta && deepest.meta.titleKey + const appTitle = i18n && typeof i18n.t === 'function' ? i18n.t('common.app.title') : 'Soda Admin' + const localized = key ? i18n.t(key) : '' + document.title = key ? `${localized} - ${appTitle}` : `${appTitle}` + } catch (e) { + // ignore + } +}) + export default router