From 2ea8b69f7bd594a9a7ee132de2b115bad35b378e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Chyeonggkim=E2=80=9D?=
<โhyeonggkim@smilegate.comโ>
Date: Fri, 19 Sep 2025 19:11:01 +0900
Subject: [PATCH] =?UTF-8?q?feat:=20=EC=96=B8=EC=96=B4=20=EB=B3=80=EA=B2=BD?=
=?UTF-8?q?=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/pages/[d1]/[d2]/index.vue | 1 +
layers/components/blocks/LanguageSwitcher.vue | 37 +++-
layers/composables/useGetGameAlias.ts | 2 +-
layers/composables/usePathResolver.ts | 12 +-
layers/layouts/promotion.vue | 4 +-
layers/middleware/pageData.global.ts | 10 +-
layers/server/middleware/gameInfo.ts | 30 ++--
layers/stores/useGameDataStore.ts | 7 +
layers/utils/dataUtil.ts | 1 -
layers/utils/localeUtil.ts | 166 ++++++++++++++++++
10 files changed, 238 insertions(+), 32 deletions(-)
create mode 100644 layers/utils/localeUtil.ts
diff --git a/app/pages/[d1]/[d2]/index.vue b/app/pages/[d1]/[d2]/index.vue
index dceae28..9013128 100644
--- a/app/pages/[d1]/[d2]/index.vue
+++ b/app/pages/[d1]/[d2]/index.vue
@@ -4,6 +4,7 @@ import { getLayoutType } from '#layers/utils/dataUtil'
const pageDataStore = usePageDataStore()
const { pageData } = storeToRefs(pageDataStore)
+console.log("๐ d2 index ~ pageData:", pageData)
const currentLayout = computed(() => getLayoutType(pageData.value))
diff --git a/layers/components/blocks/LanguageSwitcher.vue b/layers/components/blocks/LanguageSwitcher.vue
index da81a41..245d386 100644
--- a/layers/components/blocks/LanguageSwitcher.vue
+++ b/layers/components/blocks/LanguageSwitcher.vue
@@ -2,7 +2,9 @@
diff --git a/layers/middleware/pageData.global.ts b/layers/middleware/pageData.global.ts
index 0fcded0..0c05f89 100644
--- a/layers/middleware/pageData.global.ts
+++ b/layers/middleware/pageData.global.ts
@@ -9,24 +9,28 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const store = usePageDataStore()
const gameAlias = useGetGameAlias()
const { getPathAfterLanguage } = usePathResolver()
+ const headers = useRequestHeaders()
+ const langCode = ssrGetFinalLocale(to.path, headers)
const stoveApiBaseUrl = config.public.stoveApiUrl
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
try {
const pageUrl = getPathAfterLanguage(to.path)
+ console.log("๐ ~ pageUrl:", pageUrl)
// pageUrl์ด ๋น๊ฐ์ด๊ฑฐ๋ null์ด๋ฉด /brand๋ก ๋ฆฌ๋ค์ด๋ ํธ
if (!pageUrl || pageUrl === '' || pageUrl === '/') {
- return navigateTo('/brand', { replace: true })
+ return navigateTo(`/${langCode}/brand`, { replace: true })
}
const queryParams: Record = {
- game_alias: gameAlias,
- lang_code: 'ko',
+ game_domain: gameAlias,
+ lang_code: langCode,
page_url: pageUrl,
_t: Date.now().toString(), // ์บ์ ๋ฌดํจํ๋ฅผ ์ํ ํ์์คํฌํ
}
+ console.log("๐ ~ queryParams:", queryParams)
const response = (await commonFetch('GET', apiUrl, {
query: queryParams,
diff --git a/layers/server/middleware/gameInfo.ts b/layers/server/middleware/gameInfo.ts
index 572f25a..3377677 100644
--- a/layers/server/middleware/gameInfo.ts
+++ b/layers/server/middleware/gameInfo.ts
@@ -5,6 +5,8 @@ import {
getRequestURL,
} from 'h3'
+import { ssrGetFinalLocale } from '../../utils/localeUtil'
+
export default defineEventHandler(async event => {
const url = getRequestURL(event)
@@ -25,7 +27,7 @@ export default defineEventHandler(async event => {
const isGameAliasExtractable = host.includes(baseDomain)
if (isGameAliasExtractable) {
- const gameAlias = host.split('.')[0]
+ const gameAlias = host.split(':')[0]
if (gameAlias && gameAlias !== 'www') {
event.context.gameAlias = gameAlias
@@ -38,21 +40,23 @@ export default defineEventHandler(async event => {
const stoveApiBaseUrl = config.public.stoveApiUrl
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game`
+
+ const langCode = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
+
// URL์ ์ฒซ ๋ฒ์งธ path๋ฅผ lang_code๋ก ์ฌ์ฉ (ํ๋น์ฝ, API ๊ฒฝ๋ก ์ ์ธ)
- const url = getRequestURL(event)
- const pathSegments = url.pathname
- .split('/')
- .filter(
- segment =>
- segment &&
- !segment.includes('favicon') &&
- !segment.includes('api') &&
- !segment.startsWith('_')
- )
- const langCode = pathSegments[0] || 'ko'
+ // const pathSegments = url.pathname
+ // .split('/')
+ // .filter(
+ // segment =>
+ // segment &&
+ // !segment.includes('favicon') &&
+ // !segment.includes('api') &&
+ // !segment.startsWith('_')
+ // )
+ // const langCode = pathSegments[0] || 'ko'
const queryParams: Record = {
- game_alias: event.context.gameAlias || '',
+ game_domain: event.context.gameAlias || '',
lang_code: langCode,
}
diff --git a/layers/stores/useGameDataStore.ts b/layers/stores/useGameDataStore.ts
index 6492234..f22bbfe 100644
--- a/layers/stores/useGameDataStore.ts
+++ b/layers/stores/useGameDataStore.ts
@@ -2,18 +2,25 @@ import type { GameDataValue } from '#layers/types/api/gameData'
export const useGameDataStore = defineStore('gameData', () => {
const gameData = ref(null)
+ const langCode = ref(null)
const setGameData = (data: GameDataValue) => {
gameData.value = data
}
+ const setLangCode = (data: string) => {
+ langCode.value = data
+ }
+
const clearGameData = () => {
gameData.value = null
}
return {
+ langCode,
gameData,
setGameData,
+ setLangCode,
clearGameData,
}
})
diff --git a/layers/utils/dataUtil.ts b/layers/utils/dataUtil.ts
index a5bdc6e..4dfae87 100644
--- a/layers/utils/dataUtil.ts
+++ b/layers/utils/dataUtil.ts
@@ -10,7 +10,6 @@ export const getLayoutType = (
return pageData?.page_type === 1 ? 'default' : 'promotion'
}
-// ์ด๋ฏธ์ง ํธ์คํธ ๋ฆฌํดํ๋ ํจ์
// [TODO] ํ๊ฒฝ๋ณ์ ์ฒ๋ฆฌ ์์
export const getResolvedHost = (path: string): string => {
const config = useRuntimeConfig()
diff --git a/layers/utils/localeUtil.ts b/layers/utils/localeUtil.ts
new file mode 100644
index 0000000..7272e7d
--- /dev/null
+++ b/layers/utils/localeUtil.ts
@@ -0,0 +1,166 @@
+import { DEFAULT_LOCALE_CODE, DEFAULT_COVERAGES } from '@/i18n.config'
+
+// ์ฌ์ฉ์ ์ ํธ ์ธ์ด ๊ฐ์ ธ์ค๊ธฐ
+export const getPreferredLanguage = (acceptLanguageHeader = '') => {
+ const languages = acceptLanguageHeader
+ .split(',')
+ .map((lang) => {
+ const [code, priority = 'q=1'] = lang.trim().split(';q=')
+ return { code, priority: parseFloat(priority) }
+ })
+ .sort((a, b) => b.priority - a.priority)
+
+ return languages.length > 0 ? languages[0].code : null
+}
+
+export const getFinalLanguage = (path = '', defaultLocale: string, coverages: string[]) => {
+ // const nuxtApp = useNuxtApp()
+ let finalLocale = ''
+ let requestedLocale
+ let acceptLanguage: string
+ let defaultLang = 'en'
+ let defaultLangEn: string
+ if (defaultLocale) {
+ defaultLangEn = defaultLocale
+ } else {
+ defaultLangEn = 'en'
+ }
+
+ requestedLocale = path?.split('/')[1]?.toLowerCase() ?? 'undefined'
+
+ if (import.meta.server) {
+ const headers = useRequestHeaders(['accept-language'])
+ acceptLanguage = headers['accept-language'] || defaultLangEn
+
+ defaultLang =
+ coverages.find((locale: string) => getPreferredLanguage(acceptLanguage)?.startsWith(locale)) || defaultLangEn
+ }
+
+ // const DEFAULT_COVERAGES = i18n.locales.map((locale) => locale.code)
+ const DEFAULT_COVERAGES = coverages
+ const requestedPage = path?.split('/')[2]?.toLowerCase() ?? undefined
+
+ const localeMap: Record = {
+ 'zh-tw': 'zh-TW',
+ 'zh-cn': 'zh-CN'
+ }
+
+ if (localeMap[requestedLocale]) {
+ requestedLocale = localeMap[requestedLocale]
+ }
+
+ if (requestedLocale !== undefined && DEFAULT_COVERAGES.includes(requestedLocale)) {
+ finalLocale = requestedLocale
+ } else if (
+ requestedLocale === undefined ||
+ requestedLocale === '' ||
+ path !== '' ||
+ (requestedLocale !== undefined && !DEFAULT_COVERAGES.includes(requestedLocale) && requestedPage !== undefined)
+ ) {
+ // ์์ฒญ๋ ์ธ์ด๊ฐ ์์ ๋ or ์๋ชป๋ ์ธ์ด์ฝ๋๋ก ์์ฒญ ์ ๋ธ๋ผ์ฐ์ ์ธ์ด๋ก ์ค์
+ finalLocale = defaultLang
+ } else {
+ // ๊ทธ ์ธ์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ์ธ์ด๋ก ์ค์ (์ค๊ตญ์ด ๋ฒ์ฒด)
+ finalLocale = defaultLangEn
+ }
+
+ return finalLocale.toLowerCase()
+}
+
+/**
+ * ์ฐ์ ์์์ ๋ฐ๋ฅธ ์ธ์ด ์กฐํ (CSR)
+ *
+ * @param {string} path - ํ์ฌ URL ๊ฒฝ๋ก
+ */
+export const csrGetFinalLocale = (path = '') => {
+ let finalLocale = DEFAULT_LOCALE_CODE // ๊ธฐ๋ณธ๊ฐ ์ค์
+ const localeMap: Record = {
+ 'zh-tw': 'zh-TW',
+ 'zh-cn': 'zh-CN'
+ }
+
+ // 1. URL ํจ์ค์ ํฌํจ๋ ์ธ์ด
+ if (path && path !== '' && path.split('/').length > 1) {
+ const pathLocal = path.split('/')[1]
+
+ // URL ํจ์ค์ ํฌํจ๋ ์ธ์ด๊ฐ ์ง์ํ๋ ์ธ์ด์ธ์ง ์ฒดํฌ
+ if (pathLocal && pathLocal !== '' && DEFAULT_COVERAGES.includes(pathLocal)) {
+ finalLocale = pathLocal // .toLowerCase()
+
+ if (localeMap[pathLocal]) {
+ finalLocale = localeMap[pathLocal]
+ }
+ }
+ return finalLocale
+ }
+
+ // 2. ๋ธ๋ผ์ฐ์ ์ธ์ด
+ const browserLanguage = navigator.language || navigator.languages[0]
+ if (browserLanguage && browserLanguage !== '' && DEFAULT_COVERAGES.includes(browserLanguage)) {
+ finalLocale = browserLanguage // .toLowerCase()
+ if (localeMap[browserLanguage]) {
+ finalLocale = localeMap[browserLanguage]
+ }
+ return finalLocale
+ }
+
+ // 3. ์๋น์ค ๊ธฐ๋ณธ ์ธ์ด
+ finalLocale = DEFAULT_LOCALE_CODE
+
+ return finalLocale
+}
+
+/**
+ * ์ฐ์ ์์์ ๋ฐ๋ฅธ ์ธ์ด ์กฐํ (SSR)
+ *
+ * @param {string} path - ํ์ฌ URL ๊ฒฝ๋ก
+ * @param {any} headers - ์์ฒญ ํค๋
+ */
+export const ssrGetFinalLocale = (path = '', headers: any) => {
+ let finalLocale = DEFAULT_LOCALE_CODE // ๊ธฐ๋ณธ๊ฐ ์ค์
+
+
+ try {
+ // 1. URL path์ ํฌํจ๋ ์ธ์ด ์ ๋ณด
+ if (path && path !== '' && path.split('/').length > 1) {
+ const pathLocale = path.split('/')[1]
+
+ // URL path์ ํฌํจ๋ ์ธ์ด ์ ๋ณด๊ฐ ์ง์ํ๋ ์ธ์ด์ธ์ง ์ฒดํฌ
+ if (pathLocale && pathLocale !== '' && DEFAULT_COVERAGES.includes(pathLocale)) {
+ finalLocale = pathLocale // .toLowerCase()
+
+ return finalLocale
+ }
+ }
+
+ // 2. ์์ฒญ ํค๋์ ๋ธ๋ผ์ฐ์ ์ธ์ด (accept-language)
+ if (headers && headers['accept-language']) {
+ const acceptLanguage = Array.isArray(headers['accept-language'])
+ ? headers['accept-language'][0]
+ : headers['accept-language']
+
+ if (acceptLanguage && typeof acceptLanguage === 'string') {
+ const preferredLocale = getPreferredLanguage(acceptLanguage)
+ if (preferredLocale) {
+ // ์ ํธ ์ธ์ด์ ๊ธฐ๋ณธ ์ฝ๋์ ์ผ์นํ๋ ์ง์ ๋ก์ผ์ผ ์ฐพ๊ธฐ
+ const matchedLocale = DEFAULT_COVERAGES.find((locale: string) =>
+ preferredLocale.toLowerCase().startsWith(locale.toLowerCase())
+ )
+ if (matchedLocale) {
+ finalLocale = matchedLocale
+ // return matchedLocale.toLowerCase()
+
+ return finalLocale
+ }
+ }
+ }
+ }
+
+ // 3. ์๋น์ค ๊ธฐ๋ณธ ์ธ์ด
+ finalLocale = DEFAULT_LOCALE_CODE
+ } catch (e) {
+ console.error('[Exception] localeUtil.ssrGetFinalLocale: ', e)
+ finalLocale = DEFAULT_LOCALE_CODE
+ }
+ return finalLocale
+}