@spfn/cms 0.1.0-alpha.78 → 0.1.0-alpha.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { e as LocaleInfo } from './locale.constants-BMBK70YM.js';
1
+ import { f as LocaleInfo } from './locale.constants-BNkSdNP1.js';
2
2
 
3
3
  /**
4
4
  * 현재 locale 가져오기 (Server Action)
package/dist/actions.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { g as getLocale, a as getLocales, s as setLocale } from './actions-BpTAYuBA.js';
2
- export { L as LOCALE_COOKIE_KEY } from './locale.constants-BMBK70YM.js';
1
+ export { g as getLocale, a as getLocales, s as setLocale } from './actions-BEFWwQsh.js';
2
+ export { L as LOCALE_COOKIE_KEY } from './locale.constants-BNkSdNP1.js';
package/dist/actions.js CHANGED
@@ -14,13 +14,15 @@ function loadConfigFromEnv() {
14
14
  const defaultLocale = getEnvVar("SPFN_CMS_DEFAULT_LOCALE", "en");
15
15
  const supportedLocalesStr = getEnvVar("SPFN_CMS_SUPPORTED_LOCALES", "en,ko");
16
16
  const detectBrowserLanguage2 = getEnvBoolean("SPFN_CMS_DETECT_BROWSER_LANGUAGE", true);
17
- const supportedLocales = supportedLocalesStr.split(",").map((locale) => locale.trim()).filter((locale) => locale.length > 0);
18
- if (!supportedLocales.includes(defaultLocale)) {
19
- supportedLocales.unshift(defaultLocale);
17
+ const locales = supportedLocalesStr.split(",").map((locale) => locale.trim()).filter((locale) => locale.length > 0);
18
+ if (!locales.includes(defaultLocale)) {
19
+ locales.unshift(defaultLocale);
20
20
  }
21
21
  return {
22
22
  defaultLocale,
23
- supportedLocales,
23
+ locales,
24
+ supportedLocales: locales,
25
+ // backward compatibility
24
26
  detectBrowserLanguage: detectBrowserLanguage2
25
27
  };
26
28
  }
@@ -46,7 +48,7 @@ async function detectBrowserLanguage() {
46
48
  });
47
49
  const config = getCmsConfig();
48
50
  for (const lang of languages) {
49
- if (config.supportedLocales.includes(lang)) {
51
+ if (config.locales.includes(lang)) {
50
52
  return lang;
51
53
  }
52
54
  }
@@ -59,7 +61,7 @@ async function getLocale() {
59
61
  const config = getCmsConfig();
60
62
  const cookieStore = await cookies();
61
63
  const cookieLocale = cookieStore.get(LOCALE_COOKIE_KEY)?.value;
62
- if (cookieLocale && config.supportedLocales.includes(cookieLocale)) {
64
+ if (cookieLocale && config.locales.includes(cookieLocale)) {
63
65
  return cookieLocale;
64
66
  }
65
67
  if (config.detectBrowserLanguage) {
@@ -72,9 +74,9 @@ async function getLocale() {
72
74
  }
73
75
  async function setLocale(locale) {
74
76
  const config = getCmsConfig();
75
- if (!config.supportedLocales.includes(locale)) {
77
+ if (!config.locales.includes(locale)) {
76
78
  throw new Error(
77
- `Unsupported locale: ${locale}. Supported locales: ${config.supportedLocales.join(", ")}`
79
+ `Unsupported locale: ${locale}. Supported locales: ${config.locales.join(", ")}`
78
80
  );
79
81
  }
80
82
  const cookieStore = await cookies();
@@ -87,7 +89,7 @@ async function setLocale(locale) {
87
89
  }
88
90
  async function getLocales() {
89
91
  const config = getCmsConfig();
90
- return config.supportedLocales;
92
+ return config.locales;
91
93
  }
92
94
  export {
93
95
  LOCALE_COOKIE_KEY,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/helpers/locale.actions.ts","../src/server/config/cms.config.ts","../src/lib/constants/locale.constants.ts"],"sourcesContent":["\"use server\";\n\n/**\n * Locale Management Server Actions\n *\n * Server Actions으로 구현된 locale 관리 함수\n * - 서버 컴포넌트: 일반 함수 호출로 동작\n * - 클라이언트 컴포넌트: Server Action으로 자동 처리\n */\n\nimport { cookies, headers } from 'next/headers.js';\nimport { getCmsConfig } from '@/server/config/cms.config';\nimport {\n LOCALE_COOKIE_KEY,\n getLocaleInfo,\n type LocaleInfo,\n} from '@/lib/constants/locale.constants';\n\n/**\n * 브라우저 언어 감지\n *\n * Accept-Language 헤더에서 지원하는 언어를 찾습니다.\n *\n * @returns 감지된 언어 코드 또는 null\n */\nasync function detectBrowserLanguage(): Promise<string | null>\n{\n try\n {\n const headersList = await headers();\n const acceptLanguage = headersList.get('accept-language');\n\n if (!acceptLanguage)\n {\n return null;\n }\n\n // \"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\" 형식 파싱\n const languages = acceptLanguage\n .split(',')\n .map(lang =>\n {\n const [code] = lang.split(';');\n return code.split('-')[0].trim();\n });\n\n const config = getCmsConfig();\n\n // 지원하는 언어 중 첫 번째 매칭되는 언어 반환\n for (const lang of languages)\n {\n if (config.supportedLocales.includes(lang))\n {\n return lang;\n }\n }\n\n return null;\n }\n catch (error)\n {\n // 헤더 접근 실패 시\n return null;\n }\n}\n\n/**\n * 현재 locale 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * 우선순위:\n * 1. 쿠키 (사용자가 명시적으로 선택한 언어)\n * 2. 브라우저 언어 감지 (설정에서 활성화된 경우)\n * 3. 시스템 기본 언어 (CMS 설정)\n *\n * @returns 현재 locale (예: 'ko', 'en')\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locale = await getLocale();\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locale, setLocale] = useState('');\n *\n * useEffect(() => {\n * getLocale().then(setLocale);\n * }, []);\n *\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n */\nexport async function getLocale(): Promise<string>\n{\n const config = getCmsConfig();\n\n // 1순위: 쿠키 (사용자가 명시적으로 선택한 언어)\n const cookieStore = await cookies();\n const cookieLocale = cookieStore.get(LOCALE_COOKIE_KEY)?.value;\n\n if (cookieLocale && config.supportedLocales.includes(cookieLocale))\n {\n return cookieLocale;\n }\n\n // 2순위: 브라우저 언어 감지 (설정에서 활성화된 경우)\n if (config.detectBrowserLanguage)\n {\n const browserLang = await detectBrowserLanguage();\n if (browserLang)\n {\n return browserLang;\n }\n }\n\n // 3순위: 시스템 기본 언어\n return config.defaultLocale;\n}\n\n/**\n * Locale 설정하기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n * 쿠키에 locale을 저장합니다.\n *\n * @param locale - 설정할 locale (예: 'ko', 'en')\n * @throws {Error} 지원하지 않는 locale인 경우\n *\n * @example\n * ```tsx\n * // Server Component (Server Action)\n * import { setLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * await setLocale('en');\n * return <div>Locale changed</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component (Server Action)\n * 'use client';\n * import { setLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const handleChange = async (newLocale: string) =>\n * {\n * await setLocale(newLocale);\n * window.location.reload(); // 페이지 새로고침\n * };\n *\n * return (\n * <button onClick={() => handleChange('en')}>\n * Switch to English\n * </button>\n * );\n * }\n * ```\n */\nexport async function setLocale(locale: string): Promise<void>\n{\n const config = getCmsConfig();\n\n // 유효성 검사\n if (!config.supportedLocales.includes(locale))\n {\n throw new Error(\n `Unsupported locale: ${locale}. Supported locales: ${config.supportedLocales.join(', ')}`\n );\n }\n\n const cookieStore = await cookies();\n\n cookieStore.set(LOCALE_COOKIE_KEY, locale, {\n path: '/',\n maxAge: 60 * 60 * 24 * 365, // 1년\n sameSite: 'lax',\n });\n}\n\n/**\n * 지원하는 locale 목록 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * @returns 지원하는 locale 배열 (예: ['ko', 'en', 'ja'])\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocales } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locales = await getLocales();\n * return <div>Supported: {locales.join(', ')}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocales } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locales, setLocales] = useState<string[]>([]);\n *\n * useEffect(() => {\n * getLocales().then(setLocales);\n * }, []);\n *\n * return (\n * <div>\n * {locales.map(locale => (\n * <button key={locale}>{locale}</button>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocales(): Promise<string[]>\n{\n const config = getCmsConfig();\n return config.supportedLocales;\n}\n\n/**\n * 현재 locale과 상세 정보 함께 가져오기 (Server Action)\n *\n * locale 코드와 함께 국가 코드, 국기, 전화번호 코드 등의 상세 정보를 반환합니다.\n *\n * @returns Locale 코드와 LocaleInfo 객체\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocaleWithInfo } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const { locale, info } = await getLocaleWithInfo();\n *\n * return (\n * <div>\n * <span>{info?.flag}</span>\n * <span>{info?.nativeName}</span>\n * <span>{info?.dialCode}</span>\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocaleWithInfo(): Promise<{\n locale: string;\n info: LocaleInfo | undefined;\n}>\n{\n const locale = await getLocale();\n const info = getLocaleInfo(locale);\n\n return { locale, info };\n}\n\n/**\n * 지원하는 모든 locale과 상세 정보 가져오기 (Server Action)\n *\n * 시스템이 지원하는 모든 locale의 상세 정보를 배열로 반환합니다.\n * 언어 선택 UI를 만들 때 유용합니다.\n *\n * @returns LocaleInfo 배열\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocalesWithInfo } from '@spfn/cms/actions';\n *\n * export default async function LanguageSelector()\n * {\n * const locales = await getLocalesWithInfo();\n *\n * return (\n * <select>\n * {locales.map(info => (\n * <option key={info.locale} value={info.locale}>\n * {info.flag} {info.nativeName}\n * </option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport async function getLocalesWithInfo(): Promise<LocaleInfo[]>\n{\n const config = getCmsConfig();\n const supportedLocales = config.supportedLocales;\n\n return supportedLocales\n .map(locale => getLocaleInfo(locale))\n .filter((info): info is LocaleInfo => info !== undefined);\n}","/**\n * CMS Configuration Module\n *\n * 환경변수 기반 CMS 설정 관리\n * - SPFN_CMS_DEFAULT_LOCALE: 기본 언어 (기본값: 'ko')\n * - SPFN_CMS_SUPPORTED_LOCALES: 지원 언어 목록, 쉼표로 구분 (기본값: 'ko,en')\n * - SPFN_CMS_DETECT_BROWSER_LANGUAGE: 브라우저 언어 자동 감지 (기본값: 'false')\n */\n\n/**\n * CMS 설정 타입\n */\nexport interface CmsConfig\n{\n /**\n * 기본 언어 코드\n * @example 'ko', 'en', 'ja'\n */\n defaultLocale: string;\n\n /**\n * 지원하는 언어 목록\n * @example ['ko', 'en', 'ja']\n */\n supportedLocales: string[];\n\n /**\n * 브라우저 언어 자동 감지 여부\n * @default true\n */\n detectBrowserLanguage: boolean;\n}\n\n/**\n * 환경변수 읽기 헬퍼\n */\nfunction getEnvVar(key: string, defaultValue: string): string\n{\n return process.env[key] || defaultValue;\n}\n\n/**\n * 환경변수에서 boolean 읽기\n */\nfunction getEnvBoolean(key: string, defaultValue: boolean): boolean\n{\n const value = process.env[key];\n if (value === undefined) return defaultValue;\n return value === 'true' || value === '1';\n}\n\n/**\n * 환경변수에서 설정 로드\n */\nfunction loadConfigFromEnv(): CmsConfig\n{\n const defaultLocale = getEnvVar('SPFN_CMS_DEFAULT_LOCALE', 'en');\n const supportedLocalesStr = getEnvVar('SPFN_CMS_SUPPORTED_LOCALES', 'en,ko');\n const detectBrowserLanguage = getEnvBoolean('SPFN_CMS_DETECT_BROWSER_LANGUAGE', true);\n\n const supportedLocales = supportedLocalesStr\n .split(',')\n .map(locale => locale.trim())\n .filter(locale => locale.length > 0);\n\n // 기본 언어가 지원 목록에 없으면 추가\n if (!supportedLocales.includes(defaultLocale))\n {\n supportedLocales.unshift(defaultLocale);\n }\n\n return {\n defaultLocale,\n supportedLocales,\n detectBrowserLanguage,\n };\n}\n\n/**\n * 현재 설정 (환경변수에서 초기화)\n */\nlet currentConfig: CmsConfig = loadConfigFromEnv();\n\n/**\n * CMS 설정 조회\n *\n * @returns 현재 CMS 설정\n *\n * @example\n * ```tsx\n * import { getCmsConfig } from '@spfn/cms';\n *\n * const config = getCmsConfig();\n * console.log(config.defaultLocale); // 'ko'\n * console.log(config.supportedLocales); // ['ko', 'en']\n * ```\n */\nexport function getCmsConfig(): Readonly<CmsConfig>\n{\n return currentConfig;\n}\n\n/**\n * CMS 설정 변경 (런타임 오버라이드)\n *\n * 환경변수 설정을 런타임에 오버라이드합니다.\n * 주로 테스트나 특수한 경우에 사용됩니다.\n *\n * @param config - 변경할 설정 (부분 업데이트 가능)\n *\n * @example\n * ```tsx\n * import { configureCms } from '@spfn/cms';\n *\n * // 앱 초기화 시 (선택적)\n * configureCms({\n * defaultLocale: 'en',\n * supportedLocales: ['en', 'ko', 'ja'],\n * detectBrowserLanguage: true,\n * });\n * ```\n */\nexport function configureCms(config: Partial<CmsConfig>): void\n{\n currentConfig = {\n ...currentConfig,\n ...config,\n };\n\n // 기본 언어가 지원 목록에 있는지 확인\n if (config.defaultLocale && !currentConfig.supportedLocales.includes(config.defaultLocale))\n {\n console.warn(\n `[CMS Config] Default locale '${config.defaultLocale}' not in supported locales, adding automatically.`,\n `Supported locales: [${currentConfig.supportedLocales.join(', ')}]`\n );\n\n currentConfig.supportedLocales.unshift(config.defaultLocale);\n }\n}\n\n/**\n * 설정 초기화 (환경변수에서 재로드)\n *\n * @example\n * ```tsx\n * import { resetCmsConfig } from '@spfn/cms';\n *\n * // 환경변수 설정으로 되돌리기\n * resetCmsConfig();\n * ```\n */\nexport function resetCmsConfig(): void\n{\n currentConfig = loadConfigFromEnv();\n}","/**\n * Locale Constants\n *\n * Server/Client 양쪽에서 사용 가능한 locale 관련 상수\n */\n\n/**\n * Locale 쿠키 키\n */\nexport const LOCALE_COOKIE_KEY = 'spfn-locale';\n\n/**\n * 지원하는 Locale 타입 (Type-safe)\n */\nexport type SupportedLocale =\n // 아시아-태평양\n | 'ko' // 한국어\n | 'ja' // 일본어\n | 'zh' // 중국어 (간체)\n | 'zh-TW' // 중국어 (번체, 대만)\n | 'zh-HK' // 중국어 (홍콩)\n | 'hi' // 힌디어\n | 'th' // 태국어\n | 'vi' // 베트남어\n | 'id' // 인도네시아어\n | 'ms' // 말레이어\n // 영어권\n | 'en' // 영어 (미국)\n | 'en-GB' // 영어 (영국)\n | 'en-CA' // 영어 (캐나다)\n | 'en-AU' // 영어 (호주)\n | 'en-NZ' // 영어 (뉴질랜드)\n // 서유럽\n | 'es' // 스페인어 (스페인)\n | 'es-MX' // 스페인어 (멕시코)\n | 'es-AR' // 스페인어 (아르헨티나)\n | 'es-CO' // 스페인어 (콜롬비아)\n | 'fr' // 프랑스어\n | 'de' // 독일어\n | 'it' // 이탈리아어\n | 'pt' // 포르투갈어\n | 'nl' // 네덜란드어\n // 북유럽\n | 'sv' // 스웨덴어\n | 'no' // 노르웨이어\n | 'da' // 덴마크어\n | 'fi' // 핀란드어\n // 동유럽\n | 'ru' // 러시아어\n | 'pl' // 폴란드어\n | 'uk' // 우크라이나어\n | 'cs' // 체코어\n | 'hu' // 헝가리어\n | 'ro' // 루마니아어\n | 'bg' // 불가리아어\n | 'hr' // 크로아티아어\n | 'sr' // 세르비아어\n | 'sk' // 슬로바키아어\n | 'sl' // 슬로베니아어\n | 'lt' // 리투아니아어\n | 'lv' // 라트비아어\n | 'et' // 에스토니아어\n // 남유럽\n | 'el' // 그리스어\n // 중동\n | 'tr' // 터키어\n | 'ar' // 아랍어\n | 'fa' // 페르시아어\n | 'he' // 히브리어\n // 아프리카\n | 'sw'; // 스와힐리어\n\n/**\n * 국가/지역 정보 타입\n */\nexport interface LocaleInfo\n{\n /** Locale 코드 (ISO 639-1) */\n locale: SupportedLocale;\n /** 국가 코드 (ISO 3166-1 alpha-2) */\n countryCode: string;\n /** 국기 이모지 (HTML/React용) */\n flag: string;\n /** 전화번호 국가 코드 */\n dialCode: string;\n /** 네이티브 이름 (현지어) */\n nativeName: string;\n /** 영어 이름 */\n englishName: string;\n /** RTL (Right-to-Left) 여부 */\n rtl?: boolean;\n /** 통화 코드 (ISO 4217) */\n currencyCode?: string;\n /** 날짜 형식 예시 */\n dateFormat?: string;\n}\n\n/**\n * 사전 정의된 Locale 정보 맵\n *\n * 주요 언어/국가 정보를 포함합니다.\n * 프로젝트에 맞게 추가/수정 가능합니다.\n */\nexport const LOCALE_INFO_MAP: Record<SupportedLocale, LocaleInfo> = {\n // 한국어\n ko: {\n locale: 'ko',\n countryCode: 'KR',\n flag: '&#x1F1F0;&#x1F1F7;',\n dialCode: '+82',\n nativeName: '한국어',\n englishName: 'Korean',\n currencyCode: 'KRW',\n dateFormat: 'YYYY.MM.DD',\n },\n\n // 영어 (미국)\n en: {\n locale: 'en',\n countryCode: 'US',\n flag: '&#x1F1FA;&#x1F1F8;',\n dialCode: '+1',\n nativeName: 'English',\n englishName: 'English',\n currencyCode: 'USD',\n dateFormat: 'MM/DD/YYYY',\n },\n\n // 일본어\n ja: {\n locale: 'ja',\n countryCode: 'JP',\n flag: '&#x1F1EF;&#x1F1F5;',\n dialCode: '+81',\n nativeName: '日本語',\n englishName: 'Japanese',\n currencyCode: 'JPY',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 중국어 (간체)\n zh: {\n locale: 'zh',\n countryCode: 'CN',\n flag: '&#x1F1E8;&#x1F1F3;',\n dialCode: '+86',\n nativeName: '简体中文',\n englishName: 'Chinese (Simplified)',\n currencyCode: 'CNY',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 중국어 (번체, 대만)\n 'zh-TW': {\n locale: 'zh-TW',\n countryCode: 'TW',\n flag: '&#x1F1F9;&#x1F1FC;',\n dialCode: '+886',\n nativeName: '繁體中文',\n englishName: 'Chinese (Traditional)',\n currencyCode: 'TWD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 스페인어\n es: {\n locale: 'es',\n countryCode: 'ES',\n flag: '&#x1F1EA;&#x1F1F8;',\n dialCode: '+34',\n nativeName: 'Español',\n englishName: 'Spanish',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 프랑스어\n fr: {\n locale: 'fr',\n countryCode: 'FR',\n flag: '&#x1F1EB;&#x1F1F7;',\n dialCode: '+33',\n nativeName: 'Français',\n englishName: 'French',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 독일어\n de: {\n locale: 'de',\n countryCode: 'DE',\n flag: '&#x1F1E9;&#x1F1EA;',\n dialCode: '+49',\n nativeName: 'Deutsch',\n englishName: 'German',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 이탈리아어\n it: {\n locale: 'it',\n countryCode: 'IT',\n flag: '&#x1F1EE;&#x1F1F9;',\n dialCode: '+39',\n nativeName: 'Italiano',\n englishName: 'Italian',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 포르투갈어 (브라질)\n pt: {\n locale: 'pt',\n countryCode: 'BR',\n flag: '&#x1F1E7;&#x1F1F7;',\n dialCode: '+55',\n nativeName: 'Português',\n englishName: 'Portuguese',\n currencyCode: 'BRL',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 러시아어\n ru: {\n locale: 'ru',\n countryCode: 'RU',\n flag: '&#x1F1F7;&#x1F1FA;',\n dialCode: '+7',\n nativeName: 'Русский',\n englishName: 'Russian',\n currencyCode: 'RUB',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 아랍어\n ar: {\n locale: 'ar',\n countryCode: 'SA',\n flag: '&#x1F1F8;&#x1F1E6;',\n dialCode: '+966',\n nativeName: 'العربية',\n englishName: 'Arabic',\n rtl: true,\n currencyCode: 'SAR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 힌디어\n hi: {\n locale: 'hi',\n countryCode: 'IN',\n flag: '&#x1F1EE;&#x1F1F3;',\n dialCode: '+91',\n nativeName: 'हिन्दी',\n englishName: 'Hindi',\n currencyCode: 'INR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 태국어\n th: {\n locale: 'th',\n countryCode: 'TH',\n flag: '&#x1F1F9;&#x1F1ED;',\n dialCode: '+66',\n nativeName: 'ไทย',\n englishName: 'Thai',\n currencyCode: 'THB',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 베트남어\n vi: {\n locale: 'vi',\n countryCode: 'VN',\n flag: '&#x1F1FB;&#x1F1F3;',\n dialCode: '+84',\n nativeName: 'Tiếng Việt',\n englishName: 'Vietnamese',\n currencyCode: 'VND',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 인도네시아어\n id: {\n locale: 'id',\n countryCode: 'ID',\n flag: '&#x1F1EE;&#x1F1E9;',\n dialCode: '+62',\n nativeName: 'Bahasa Indonesia',\n englishName: 'Indonesian',\n currencyCode: 'IDR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 터키어\n tr: {\n locale: 'tr',\n countryCode: 'TR',\n flag: '&#x1F1F9;&#x1F1F7;',\n dialCode: '+90',\n nativeName: 'Türkçe',\n englishName: 'Turkish',\n currencyCode: 'TRY',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 폴란드어\n pl: {\n locale: 'pl',\n countryCode: 'PL',\n flag: '&#x1F1F5;&#x1F1F1;',\n dialCode: '+48',\n nativeName: 'Polski',\n englishName: 'Polish',\n currencyCode: 'PLN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 네덜란드어\n nl: {\n locale: 'nl',\n countryCode: 'NL',\n flag: '&#x1F1F3;&#x1F1F1;',\n dialCode: '+31',\n nativeName: 'Nederlands',\n englishName: 'Dutch',\n currencyCode: 'EUR',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 중국어 (홍콩)\n 'zh-HK': {\n locale: 'zh-HK',\n countryCode: 'HK',\n flag: '&#x1F1ED;&#x1F1F0;',\n dialCode: '+852',\n nativeName: '繁體中文 (香港)',\n englishName: 'Chinese (Hong Kong)',\n currencyCode: 'HKD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 말레이어\n ms: {\n locale: 'ms',\n countryCode: 'MY',\n flag: '&#x1F1F2;&#x1F1FE;',\n dialCode: '+60',\n nativeName: 'Bahasa Melayu',\n englishName: 'Malay',\n currencyCode: 'MYR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (영국)\n 'en-GB': {\n locale: 'en-GB',\n countryCode: 'GB',\n flag: '&#x1F1EC;&#x1F1E7;',\n dialCode: '+44',\n nativeName: 'English (UK)',\n englishName: 'English (United Kingdom)',\n currencyCode: 'GBP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (캐나다)\n 'en-CA': {\n locale: 'en-CA',\n countryCode: 'CA',\n flag: '&#x1F1E8;&#x1F1E6;',\n dialCode: '+1',\n nativeName: 'English (Canada)',\n englishName: 'English (Canada)',\n currencyCode: 'CAD',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 영어 (호주)\n 'en-AU': {\n locale: 'en-AU',\n countryCode: 'AU',\n flag: '&#x1F1E6;&#x1F1FA;',\n dialCode: '+61',\n nativeName: 'English (Australia)',\n englishName: 'English (Australia)',\n currencyCode: 'AUD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (뉴질랜드)\n 'en-NZ': {\n locale: 'en-NZ',\n countryCode: 'NZ',\n flag: '&#x1F1F3;&#x1F1FF;',\n dialCode: '+64',\n nativeName: 'English (New Zealand)',\n englishName: 'English (New Zealand)',\n currencyCode: 'NZD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (멕시코)\n 'es-MX': {\n locale: 'es-MX',\n countryCode: 'MX',\n flag: '&#x1F1F2;&#x1F1FD;',\n dialCode: '+52',\n nativeName: 'Español (México)',\n englishName: 'Spanish (Mexico)',\n currencyCode: 'MXN',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (아르헨티나)\n 'es-AR': {\n locale: 'es-AR',\n countryCode: 'AR',\n flag: '&#x1F1E6;&#x1F1F7;',\n dialCode: '+54',\n nativeName: 'Español (Argentina)',\n englishName: 'Spanish (Argentina)',\n currencyCode: 'ARS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (콜롬비아)\n 'es-CO': {\n locale: 'es-CO',\n countryCode: 'CO',\n flag: '&#x1F1E8;&#x1F1F4;',\n dialCode: '+57',\n nativeName: 'Español (Colombia)',\n englishName: 'Spanish (Colombia)',\n currencyCode: 'COP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스웨덴어\n sv: {\n locale: 'sv',\n countryCode: 'SE',\n flag: '&#x1F1F8;&#x1F1EA;',\n dialCode: '+46',\n nativeName: 'Svenska',\n englishName: 'Swedish',\n currencyCode: 'SEK',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 노르웨이어\n no: {\n locale: 'no',\n countryCode: 'NO',\n flag: '&#x1F1F3;&#x1F1F4;',\n dialCode: '+47',\n nativeName: 'Norsk',\n englishName: 'Norwegian',\n currencyCode: 'NOK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 덴마크어\n da: {\n locale: 'da',\n countryCode: 'DK',\n flag: '&#x1F1E9;&#x1F1F0;',\n dialCode: '+45',\n nativeName: 'Dansk',\n englishName: 'Danish',\n currencyCode: 'DKK',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 핀란드어\n fi: {\n locale: 'fi',\n countryCode: 'FI',\n flag: '&#x1F1EB;&#x1F1EE;',\n dialCode: '+358',\n nativeName: 'Suomi',\n englishName: 'Finnish',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 우크라이나어\n uk: {\n locale: 'uk',\n countryCode: 'UA',\n flag: '&#x1F1FA;&#x1F1E6;',\n dialCode: '+380',\n nativeName: 'Українська',\n englishName: 'Ukrainian',\n currencyCode: 'UAH',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 체코어\n cs: {\n locale: 'cs',\n countryCode: 'CZ',\n flag: '&#x1F1E8;&#x1F1FF;',\n dialCode: '+420',\n nativeName: 'Čeština',\n englishName: 'Czech',\n currencyCode: 'CZK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 헝가리어\n hu: {\n locale: 'hu',\n countryCode: 'HU',\n flag: '&#x1F1ED;&#x1F1FA;',\n dialCode: '+36',\n nativeName: 'Magyar',\n englishName: 'Hungarian',\n currencyCode: 'HUF',\n dateFormat: 'YYYY.MM.DD.',\n },\n\n // 루마니아어\n ro: {\n locale: 'ro',\n countryCode: 'RO',\n flag: '&#x1F1F7;&#x1F1F4;',\n dialCode: '+40',\n nativeName: 'Română',\n englishName: 'Romanian',\n currencyCode: 'RON',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 불가리아어\n bg: {\n locale: 'bg',\n countryCode: 'BG',\n flag: '&#x1F1E7;&#x1F1EC;',\n dialCode: '+359',\n nativeName: 'Български',\n englishName: 'Bulgarian',\n currencyCode: 'BGN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 크로아티아어\n hr: {\n locale: 'hr',\n countryCode: 'HR',\n flag: '&#x1F1ED;&#x1F1F7;',\n dialCode: '+385',\n nativeName: 'Hrvatski',\n englishName: 'Croatian',\n currencyCode: 'HRK',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 세르비아어\n sr: {\n locale: 'sr',\n countryCode: 'RS',\n flag: '&#x1F1F7;&#x1F1F8;',\n dialCode: '+381',\n nativeName: 'Српски',\n englishName: 'Serbian',\n currencyCode: 'RSD',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 슬로바키아어\n sk: {\n locale: 'sk',\n countryCode: 'SK',\n flag: '&#x1F1F8;&#x1F1F0;',\n dialCode: '+421',\n nativeName: 'Slovenčina',\n englishName: 'Slovak',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 슬로베니아어\n sl: {\n locale: 'sl',\n countryCode: 'SI',\n flag: '&#x1F1F8;&#x1F1EE;',\n dialCode: '+386',\n nativeName: 'Slovenščina',\n englishName: 'Slovenian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 리투아니아어\n lt: {\n locale: 'lt',\n countryCode: 'LT',\n flag: '&#x1F1F1;&#x1F1F9;',\n dialCode: '+370',\n nativeName: 'Lietuvių',\n englishName: 'Lithuanian',\n currencyCode: 'EUR',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 라트비아어\n lv: {\n locale: 'lv',\n countryCode: 'LV',\n flag: '&#x1F1F1;&#x1F1FB;',\n dialCode: '+371',\n nativeName: 'Latviešu',\n englishName: 'Latvian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 에스토니아어\n et: {\n locale: 'et',\n countryCode: 'EE',\n flag: '&#x1F1EA;&#x1F1EA;',\n dialCode: '+372',\n nativeName: 'Eesti',\n englishName: 'Estonian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 그리스어\n el: {\n locale: 'el',\n countryCode: 'GR',\n flag: '&#x1F1EC;&#x1F1F7;',\n dialCode: '+30',\n nativeName: 'Ελληνικά',\n englishName: 'Greek',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 페르시아어\n fa: {\n locale: 'fa',\n countryCode: 'IR',\n flag: '&#x1F1EE;&#x1F1F7;',\n dialCode: '+98',\n nativeName: 'فارسی',\n englishName: 'Persian',\n rtl: true,\n currencyCode: 'IRR',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 히브리어\n he: {\n locale: 'he',\n countryCode: 'IL',\n flag: '&#x1F1EE;&#x1F1F1;',\n dialCode: '+972',\n nativeName: 'עברית',\n englishName: 'Hebrew',\n rtl: true,\n currencyCode: 'ILS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스와힐리어\n sw: {\n locale: 'sw',\n countryCode: 'KE',\n flag: '&#x1F1F0;&#x1F1EA;',\n dialCode: '+254',\n nativeName: 'Kiswahili',\n englishName: 'Swahili',\n currencyCode: 'KES',\n dateFormat: 'DD/MM/YYYY',\n },\n};\n\n/**\n * Locale 정보 가져오기\n *\n * @param locale - Locale 코드 (예: 'ko', 'en', 'ja')\n * @returns LocaleInfo 또는 undefined\n *\n * @example\n * ```typescript\n * const koInfo = getLocaleInfo('ko');\n * console.log(koInfo.flag); // 🇰🇷\n * console.log(koInfo.dialCode); // +82\n * ```\n */\nexport function getLocaleInfo(locale: string): LocaleInfo | undefined\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale];\n}\n\n/**\n * 지원하는 모든 Locale 목록 가져오기\n *\n * @returns Locale 코드 배열\n */\nexport function getSupportedLocales(): SupportedLocale[]\n{\n return Object.keys(LOCALE_INFO_MAP) as SupportedLocale[];\n}\n\n/**\n * 국기 이모지만 가져오기\n *\n * @param locale - Locale 코드\n * @returns 국기 이모지 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getFlag('ko'); // 🇰🇷\n * getFlag('en'); // 🇺🇸\n * ```\n */\nexport function getFlag(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.flag ?? '';\n}\n\n/**\n * 전화번호 국가 코드 가져오기\n *\n * @param locale - Locale 코드\n * @returns 전화번호 코드 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getDialCode('ko'); // +82\n * getDialCode('en'); // +1\n * ```\n */\nexport function getDialCode(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.dialCode ?? '';\n}\n\n/**\n * RTL (Right-to-Left) 여부 확인\n *\n * @param locale - Locale 코드\n * @returns RTL 여부\n *\n * @example\n * ```typescript\n * isRTL('ar'); // true (Arabic)\n * isRTL('ko'); // false (Korean)\n * ```\n */\nexport function isRTL(locale: string): boolean\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.rtl ?? false;\n}"],"mappings":";AAUA,SAAS,SAAS,eAAe;;;AC0BjC,SAAS,UAAU,KAAa,cAChC;AACI,SAAO,QAAQ,IAAI,GAAG,KAAK;AAC/B;AAKA,SAAS,cAAc,KAAa,cACpC;AACI,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,UAAU,UAAU,UAAU;AACzC;AAKA,SAAS,oBACT;AACI,QAAM,gBAAgB,UAAU,2BAA2B,IAAI;AAC/D,QAAM,sBAAsB,UAAU,8BAA8B,OAAO;AAC3E,QAAMA,yBAAwB,cAAc,oCAAoC,IAAI;AAEpF,QAAM,mBAAmB,oBACpB,MAAM,GAAG,EACT,IAAI,YAAU,OAAO,KAAK,CAAC,EAC3B,OAAO,YAAU,OAAO,SAAS,CAAC;AAGvC,MAAI,CAAC,iBAAiB,SAAS,aAAa,GAC5C;AACI,qBAAiB,QAAQ,aAAa;AAAA,EAC1C;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA,uBAAAA;AAAA,EACJ;AACJ;AAKA,IAAI,gBAA2B,kBAAkB;AAgB1C,SAAS,eAChB;AACI,SAAO;AACX;;;AC3FO,IAAM,oBAAoB;;;AFgBjC,eAAe,wBACf;AACI,MACA;AACI,UAAM,cAAc,MAAM,QAAQ;AAClC,UAAM,iBAAiB,YAAY,IAAI,iBAAiB;AAExD,QAAI,CAAC,gBACL;AACI,aAAO;AAAA,IACX;AAGA,UAAM,YAAY,eACb,MAAM,GAAG,EACT,IAAI,UACL;AACI,YAAM,CAAC,IAAI,IAAI,KAAK,MAAM,GAAG;AAC7B,aAAO,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK;AAAA,IACnC,CAAC;AAEL,UAAM,SAAS,aAAa;AAG5B,eAAW,QAAQ,WACnB;AACI,UAAI,OAAO,iBAAiB,SAAS,IAAI,GACzC;AACI,eAAO;AAAA,MACX;AAAA,IACJ;AAEA,WAAO;AAAA,EACX,SACO,OACP;AAEI,WAAO;AAAA,EACX;AACJ;AA4CA,eAAsB,YACtB;AACI,QAAM,SAAS,aAAa;AAG5B,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,eAAe,YAAY,IAAI,iBAAiB,GAAG;AAEzD,MAAI,gBAAgB,OAAO,iBAAiB,SAAS,YAAY,GACjE;AACI,WAAO;AAAA,EACX;AAGA,MAAI,OAAO,uBACX;AACI,UAAM,cAAc,MAAM,sBAAsB;AAChD,QAAI,aACJ;AACI,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,SAAO,OAAO;AAClB;AA6CA,eAAsB,UAAU,QAChC;AACI,QAAM,SAAS,aAAa;AAG5B,MAAI,CAAC,OAAO,iBAAiB,SAAS,MAAM,GAC5C;AACI,UAAM,IAAI;AAAA,MACN,uBAAuB,MAAM,wBAAwB,OAAO,iBAAiB,KAAK,IAAI,CAAC;AAAA,IAC3F;AAAA,EACJ;AAEA,QAAM,cAAc,MAAM,QAAQ;AAElC,cAAY,IAAI,mBAAmB,QAAQ;AAAA,IACvC,MAAM;AAAA,IACN,QAAQ,KAAK,KAAK,KAAK;AAAA;AAAA,IACvB,UAAU;AAAA,EACd,CAAC;AACL;AA6CA,eAAsB,aACtB;AACI,QAAM,SAAS,aAAa;AAC5B,SAAO,OAAO;AAClB;","names":["detectBrowserLanguage"]}
1
+ {"version":3,"sources":["../src/server/helpers/locale.actions.ts","../src/server/config/cms.config.ts","../src/lib/constants/locale.constants.ts"],"sourcesContent":["\"use server\";\n\n/**\n * Locale Management Server Actions\n *\n * Server Actions으로 구현된 locale 관리 함수\n * - 서버 컴포넌트: 일반 함수 호출로 동작\n * - 클라이언트 컴포넌트: Server Action으로 자동 처리\n */\n\nimport { cookies, headers } from 'next/headers.js';\nimport { getCmsConfig } from '@/server/config/cms.config';\nimport {\n LOCALE_COOKIE_KEY,\n getLocaleInfo,\n type LocaleInfo,\n} from '@/lib/constants/locale.constants';\n\n/**\n * 브라우저 언어 감지\n *\n * Accept-Language 헤더에서 지원하는 언어를 찾습니다.\n *\n * @returns 감지된 언어 코드 또는 null\n */\nasync function detectBrowserLanguage(): Promise<string | null>\n{\n try\n {\n const headersList = await headers();\n const acceptLanguage = headersList.get('accept-language');\n\n if (!acceptLanguage)\n {\n return null;\n }\n\n // \"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\" 형식 파싱\n const languages = acceptLanguage\n .split(',')\n .map(lang =>\n {\n const [code] = lang.split(';');\n return code.split('-')[0].trim();\n });\n\n const config = getCmsConfig();\n\n // 지원하는 언어 중 첫 번째 매칭되는 언어 반환\n for (const lang of languages)\n {\n if (config.locales.includes(lang))\n {\n return lang;\n }\n }\n\n return null;\n }\n catch (error)\n {\n // 헤더 접근 실패 시\n return null;\n }\n}\n\n/**\n * 현재 locale 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * 우선순위:\n * 1. 쿠키 (사용자가 명시적으로 선택한 언어)\n * 2. 브라우저 언어 감지 (설정에서 활성화된 경우)\n * 3. 시스템 기본 언어 (CMS 설정)\n *\n * @returns 현재 locale (예: 'ko', 'en')\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locale = await getLocale();\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locale, setLocale] = useState('');\n *\n * useEffect(() => {\n * getLocale().then(setLocale);\n * }, []);\n *\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n */\nexport async function getLocale(): Promise<string>\n{\n const config = getCmsConfig();\n\n // 1순위: 쿠키 (사용자가 명시적으로 선택한 언어)\n const cookieStore = await cookies();\n const cookieLocale = cookieStore.get(LOCALE_COOKIE_KEY)?.value;\n\n if (cookieLocale && config.locales.includes(cookieLocale))\n {\n return cookieLocale;\n }\n\n // 2순위: 브라우저 언어 감지 (설정에서 활성화된 경우)\n if (config.detectBrowserLanguage)\n {\n const browserLang = await detectBrowserLanguage();\n if (browserLang)\n {\n return browserLang;\n }\n }\n\n // 3순위: 시스템 기본 언어\n return config.defaultLocale;\n}\n\n/**\n * Locale 설정하기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n * 쿠키에 locale을 저장합니다.\n *\n * @param locale - 설정할 locale (예: 'ko', 'en')\n * @throws {Error} 지원하지 않는 locale인 경우\n *\n * @example\n * ```tsx\n * // Server Component (Server Action)\n * import { setLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * await setLocale('en');\n * return <div>Locale changed</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component (Server Action)\n * 'use client';\n * import { setLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const handleChange = async (newLocale: string) =>\n * {\n * await setLocale(newLocale);\n * window.location.reload(); // 페이지 새로고침\n * };\n *\n * return (\n * <button onClick={() => handleChange('en')}>\n * Switch to English\n * </button>\n * );\n * }\n * ```\n */\nexport async function setLocale(locale: string): Promise<void>\n{\n const config = getCmsConfig();\n\n // 유효성 검사\n if (!config.locales.includes(locale))\n {\n throw new Error(\n `Unsupported locale: ${locale}. Supported locales: ${config.locales.join(', ')}`\n );\n }\n\n const cookieStore = await cookies();\n\n cookieStore.set(LOCALE_COOKIE_KEY, locale, {\n path: '/',\n maxAge: 60 * 60 * 24 * 365, // 1년\n sameSite: 'lax',\n });\n}\n\n/**\n * 지원하는 locale 목록 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * @returns 지원하는 locale 배열 (예: ['ko', 'en', 'ja'])\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocales } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locales = await getLocales();\n * return <div>Supported: {locales.join(', ')}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocales } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locales, setLocales] = useState<string[]>([]);\n *\n * useEffect(() => {\n * getLocales().then(setLocales);\n * }, []);\n *\n * return (\n * <div>\n * {locales.map(locale => (\n * <button key={locale}>{locale}</button>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocales(): Promise<string[]>\n{\n const config = getCmsConfig();\n return config.locales;\n}\n\n/**\n * 현재 locale과 상세 정보 함께 가져오기 (Server Action)\n *\n * locale 코드와 함께 국가 코드, 국기, 전화번호 코드 등의 상세 정보를 반환합니다.\n *\n * @returns Locale 코드와 LocaleInfo 객체\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocaleWithInfo } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const { locale, info } = await getLocaleWithInfo();\n *\n * return (\n * <div>\n * <span>{info?.flag}</span>\n * <span>{info?.nativeName}</span>\n * <span>{info?.dialCode}</span>\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocaleWithInfo(): Promise<{\n locale: string;\n info: LocaleInfo | undefined;\n}>\n{\n const locale = await getLocale();\n const info = getLocaleInfo(locale);\n\n return { locale, info };\n}\n\n/**\n * 지원하는 모든 locale과 상세 정보 가져오기 (Server Action)\n *\n * 시스템이 지원하는 모든 locale의 상세 정보를 배열로 반환합니다.\n * 언어 선택 UI를 만들 때 유용합니다.\n *\n * @returns LocaleInfo 배열\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocalesWithInfo } from '@spfn/cms/actions';\n *\n * export default async function LanguageSelector()\n * {\n * const locales = await getLocalesWithInfo();\n *\n * return (\n * <select>\n * {locales.map(info => (\n * <option key={info.locale} value={info.locale}>\n * {info.flag} {info.nativeName}\n * </option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport async function getLocalesWithInfo(): Promise<LocaleInfo[]>\n{\n const config = getCmsConfig();\n const locales = config.locales;\n\n return locales\n .map(locale => getLocaleInfo(locale))\n .filter((info): info is LocaleInfo => info !== undefined);\n}","/**\n * CMS Configuration Module\n *\n * 환경변수 기반 CMS 설정 관리\n * - SPFN_CMS_DEFAULT_LOCALE: 기본 언어 (기본값: 'ko')\n * - SPFN_CMS_SUPPORTED_LOCALES: 지원 언어 목록, 쉼표로 구분 (기본값: 'ko,en')\n * - SPFN_CMS_DETECT_BROWSER_LANGUAGE: 브라우저 언어 자동 감지 (기본값: 'false')\n */\n\n/**\n * CMS 설정 타입\n */\nexport interface CmsConfig\n{\n /**\n * 기본 언어 코드\n * @example 'ko', 'en', 'ja'\n */\n defaultLocale: string;\n\n /**\n * 프로젝트에서 사용할 언어 목록\n * @example ['ko', 'en', 'ja']\n */\n locales: string[];\n\n /**\n * @deprecated Use 'locales' instead\n * @internal For backward compatibility\n */\n supportedLocales: string[];\n\n /**\n * 브라우저 언어 자동 감지 여부\n * @default true\n */\n detectBrowserLanguage: boolean;\n}\n\n/**\n * 환경변수 읽기 헬퍼\n */\nfunction getEnvVar(key: string, defaultValue: string): string\n{\n return process.env[key] || defaultValue;\n}\n\n/**\n * 환경변수에서 boolean 읽기\n */\nfunction getEnvBoolean(key: string, defaultValue: boolean): boolean\n{\n const value = process.env[key];\n if (value === undefined) return defaultValue;\n return value === 'true' || value === '1';\n}\n\n/**\n * 환경변수에서 설정 로드\n */\nfunction loadConfigFromEnv(): CmsConfig\n{\n const defaultLocale = getEnvVar('SPFN_CMS_DEFAULT_LOCALE', 'en');\n const supportedLocalesStr = getEnvVar('SPFN_CMS_SUPPORTED_LOCALES', 'en,ko');\n const detectBrowserLanguage = getEnvBoolean('SPFN_CMS_DETECT_BROWSER_LANGUAGE', true);\n\n const locales = supportedLocalesStr\n .split(',')\n .map(locale => locale.trim())\n .filter(locale => locale.length > 0);\n\n // 기본 언어가 지원 목록에 없으면 추가\n if (!locales.includes(defaultLocale))\n {\n locales.unshift(defaultLocale);\n }\n\n return {\n defaultLocale,\n locales,\n supportedLocales: locales, // backward compatibility\n detectBrowserLanguage,\n };\n}\n\n/**\n * 현재 설정 (환경변수에서 초기화)\n */\nlet currentConfig: CmsConfig = loadConfigFromEnv();\n\n/**\n * CMS 설정 조회\n *\n * @returns 현재 CMS 설정\n *\n * @example\n * ```tsx\n * import { getCmsConfig } from '@spfn/cms';\n *\n * const config = getCmsConfig();\n * console.log(config.defaultLocale); // 'en'\n * console.log(config.locales); // ['en', 'ko']\n * ```\n */\nexport function getCmsConfig(): Readonly<CmsConfig>\n{\n return currentConfig;\n}\n\n/**\n * CMS 설정 변경 (런타임 오버라이드)\n *\n * 환경변수 설정을 런타임에 오버라이드합니다.\n * 주로 테스트나 특수한 경우에 사용됩니다.\n *\n * @param config - 변경할 설정 (부분 업데이트 가능)\n *\n * @example\n * ```tsx\n * import { configureCms } from '@spfn/cms';\n *\n * // 앱 초기화 시 (선택적)\n * configureCms({\n * defaultLocale: 'en',\n * locales: ['en', 'ko', 'ja'],\n * detectBrowserLanguage: true,\n * });\n * ```\n */\nexport function configureCms(config: Partial<CmsConfig>): void\n{\n // Backward compatibility: supportedLocales → locales\n if (config.supportedLocales && !config.locales)\n {\n config = { ...config, locales: config.supportedLocales };\n }\n\n currentConfig = {\n ...currentConfig,\n ...config,\n };\n\n // Sync locales ↔ supportedLocales\n if (config.locales)\n {\n currentConfig.supportedLocales = config.locales;\n }\n else if (config.supportedLocales)\n {\n currentConfig.locales = config.supportedLocales;\n }\n\n // 기본 언어가 지원 목록에 있는지 확인\n if (config.defaultLocale && !currentConfig.locales.includes(config.defaultLocale))\n {\n console.warn(\n `[CMS Config] Default locale '${config.defaultLocale}' not in locales, adding automatically.`,\n `Locales: [${currentConfig.locales.join(', ')}]`\n );\n\n currentConfig.locales.unshift(config.defaultLocale);\n currentConfig.supportedLocales.unshift(config.defaultLocale);\n }\n}\n\n/**\n * 설정 초기화 (환경변수에서 재로드)\n *\n * @example\n * ```tsx\n * import { resetCmsConfig } from '@spfn/cms';\n *\n * // 환경변수 설정으로 되돌리기\n * resetCmsConfig();\n * ```\n */\nexport function resetCmsConfig(): void\n{\n currentConfig = loadConfigFromEnv();\n}","/**\n * Locale Constants\n *\n * Server/Client 양쪽에서 사용 가능한 locale 관련 상수\n */\n\n/**\n * Locale 쿠키 키\n */\nexport const LOCALE_COOKIE_KEY = 'spfn-locale';\n\n/**\n * 지원하는 Locale 타입 (Type-safe)\n */\nexport type SupportedLocale =\n // 아시아-태평양\n | 'ko' // 한국어\n | 'ja' // 일본어\n | 'zh' // 중국어 (간체)\n | 'zh-TW' // 중국어 (번체, 대만)\n | 'zh-HK' // 중국어 (홍콩)\n | 'hi' // 힌디어\n | 'th' // 태국어\n | 'vi' // 베트남어\n | 'id' // 인도네시아어\n | 'ms' // 말레이어\n // 영어권\n | 'en' // 영어 (미국)\n | 'en-GB' // 영어 (영국)\n | 'en-CA' // 영어 (캐나다)\n | 'en-AU' // 영어 (호주)\n | 'en-NZ' // 영어 (뉴질랜드)\n // 서유럽\n | 'es' // 스페인어 (스페인)\n | 'es-MX' // 스페인어 (멕시코)\n | 'es-AR' // 스페인어 (아르헨티나)\n | 'es-CO' // 스페인어 (콜롬비아)\n | 'fr' // 프랑스어\n | 'de' // 독일어\n | 'it' // 이탈리아어\n | 'pt' // 포르투갈어\n | 'nl' // 네덜란드어\n // 북유럽\n | 'sv' // 스웨덴어\n | 'no' // 노르웨이어\n | 'da' // 덴마크어\n | 'fi' // 핀란드어\n // 동유럽\n | 'ru' // 러시아어\n | 'pl' // 폴란드어\n | 'uk' // 우크라이나어\n | 'cs' // 체코어\n | 'hu' // 헝가리어\n | 'ro' // 루마니아어\n | 'bg' // 불가리아어\n | 'hr' // 크로아티아어\n | 'sr' // 세르비아어\n | 'sk' // 슬로바키아어\n | 'sl' // 슬로베니아어\n | 'lt' // 리투아니아어\n | 'lv' // 라트비아어\n | 'et' // 에스토니아어\n // 남유럽\n | 'el' // 그리스어\n // 중동\n | 'tr' // 터키어\n | 'ar' // 아랍어\n | 'fa' // 페르시아어\n | 'he' // 히브리어\n // 아프리카\n | 'sw'; // 스와힐리어\n\n/**\n * 국가/지역 정보 타입\n */\nexport interface LocaleInfo\n{\n /** Locale 코드 (ISO 639-1) */\n locale: SupportedLocale;\n /** 국가 코드 (ISO 3166-1 alpha-2) */\n countryCode: string;\n /** 국기 이모지 (HTML/React용) */\n flag: string;\n /** 전화번호 국가 코드 */\n dialCode: string;\n /** 네이티브 이름 (현지어) */\n nativeName: string;\n /** 영어 이름 */\n englishName: string;\n /** RTL (Right-to-Left) 여부 */\n rtl?: boolean;\n /** 통화 코드 (ISO 4217) */\n currencyCode?: string;\n /** 날짜 형식 예시 */\n dateFormat?: string;\n}\n\n/**\n * 사전 정의된 Locale 정보 맵\n *\n * 주요 언어/국가 정보를 포함합니다.\n * 프로젝트에 맞게 추가/수정 가능합니다.\n */\nexport const LOCALE_INFO_MAP: Record<SupportedLocale, LocaleInfo> = {\n // 한국어\n ko: {\n locale: 'ko',\n countryCode: 'KR',\n flag: '&#x1F1F0;&#x1F1F7;',\n dialCode: '+82',\n nativeName: '한국어',\n englishName: 'Korean',\n currencyCode: 'KRW',\n dateFormat: 'YYYY.MM.DD',\n },\n\n // 영어 (미국)\n en: {\n locale: 'en',\n countryCode: 'US',\n flag: '&#x1F1FA;&#x1F1F8;',\n dialCode: '+1',\n nativeName: 'English',\n englishName: 'English',\n currencyCode: 'USD',\n dateFormat: 'MM/DD/YYYY',\n },\n\n // 일본어\n ja: {\n locale: 'ja',\n countryCode: 'JP',\n flag: '&#x1F1EF;&#x1F1F5;',\n dialCode: '+81',\n nativeName: '日本語',\n englishName: 'Japanese',\n currencyCode: 'JPY',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 중국어 (간체)\n zh: {\n locale: 'zh',\n countryCode: 'CN',\n flag: '&#x1F1E8;&#x1F1F3;',\n dialCode: '+86',\n nativeName: '简体中文',\n englishName: 'Chinese (Simplified)',\n currencyCode: 'CNY',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 중국어 (번체, 대만)\n 'zh-TW': {\n locale: 'zh-TW',\n countryCode: 'TW',\n flag: '&#x1F1F9;&#x1F1FC;',\n dialCode: '+886',\n nativeName: '繁體中文',\n englishName: 'Chinese (Traditional)',\n currencyCode: 'TWD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 스페인어\n es: {\n locale: 'es',\n countryCode: 'ES',\n flag: '&#x1F1EA;&#x1F1F8;',\n dialCode: '+34',\n nativeName: 'Español',\n englishName: 'Spanish',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 프랑스어\n fr: {\n locale: 'fr',\n countryCode: 'FR',\n flag: '&#x1F1EB;&#x1F1F7;',\n dialCode: '+33',\n nativeName: 'Français',\n englishName: 'French',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 독일어\n de: {\n locale: 'de',\n countryCode: 'DE',\n flag: '&#x1F1E9;&#x1F1EA;',\n dialCode: '+49',\n nativeName: 'Deutsch',\n englishName: 'German',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 이탈리아어\n it: {\n locale: 'it',\n countryCode: 'IT',\n flag: '&#x1F1EE;&#x1F1F9;',\n dialCode: '+39',\n nativeName: 'Italiano',\n englishName: 'Italian',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 포르투갈어 (브라질)\n pt: {\n locale: 'pt',\n countryCode: 'BR',\n flag: '&#x1F1E7;&#x1F1F7;',\n dialCode: '+55',\n nativeName: 'Português',\n englishName: 'Portuguese',\n currencyCode: 'BRL',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 러시아어\n ru: {\n locale: 'ru',\n countryCode: 'RU',\n flag: '&#x1F1F7;&#x1F1FA;',\n dialCode: '+7',\n nativeName: 'Русский',\n englishName: 'Russian',\n currencyCode: 'RUB',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 아랍어\n ar: {\n locale: 'ar',\n countryCode: 'SA',\n flag: '&#x1F1F8;&#x1F1E6;',\n dialCode: '+966',\n nativeName: 'العربية',\n englishName: 'Arabic',\n rtl: true,\n currencyCode: 'SAR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 힌디어\n hi: {\n locale: 'hi',\n countryCode: 'IN',\n flag: '&#x1F1EE;&#x1F1F3;',\n dialCode: '+91',\n nativeName: 'हिन्दी',\n englishName: 'Hindi',\n currencyCode: 'INR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 태국어\n th: {\n locale: 'th',\n countryCode: 'TH',\n flag: '&#x1F1F9;&#x1F1ED;',\n dialCode: '+66',\n nativeName: 'ไทย',\n englishName: 'Thai',\n currencyCode: 'THB',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 베트남어\n vi: {\n locale: 'vi',\n countryCode: 'VN',\n flag: '&#x1F1FB;&#x1F1F3;',\n dialCode: '+84',\n nativeName: 'Tiếng Việt',\n englishName: 'Vietnamese',\n currencyCode: 'VND',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 인도네시아어\n id: {\n locale: 'id',\n countryCode: 'ID',\n flag: '&#x1F1EE;&#x1F1E9;',\n dialCode: '+62',\n nativeName: 'Bahasa Indonesia',\n englishName: 'Indonesian',\n currencyCode: 'IDR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 터키어\n tr: {\n locale: 'tr',\n countryCode: 'TR',\n flag: '&#x1F1F9;&#x1F1F7;',\n dialCode: '+90',\n nativeName: 'Türkçe',\n englishName: 'Turkish',\n currencyCode: 'TRY',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 폴란드어\n pl: {\n locale: 'pl',\n countryCode: 'PL',\n flag: '&#x1F1F5;&#x1F1F1;',\n dialCode: '+48',\n nativeName: 'Polski',\n englishName: 'Polish',\n currencyCode: 'PLN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 네덜란드어\n nl: {\n locale: 'nl',\n countryCode: 'NL',\n flag: '&#x1F1F3;&#x1F1F1;',\n dialCode: '+31',\n nativeName: 'Nederlands',\n englishName: 'Dutch',\n currencyCode: 'EUR',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 중국어 (홍콩)\n 'zh-HK': {\n locale: 'zh-HK',\n countryCode: 'HK',\n flag: '&#x1F1ED;&#x1F1F0;',\n dialCode: '+852',\n nativeName: '繁體中文 (香港)',\n englishName: 'Chinese (Hong Kong)',\n currencyCode: 'HKD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 말레이어\n ms: {\n locale: 'ms',\n countryCode: 'MY',\n flag: '&#x1F1F2;&#x1F1FE;',\n dialCode: '+60',\n nativeName: 'Bahasa Melayu',\n englishName: 'Malay',\n currencyCode: 'MYR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (영국)\n 'en-GB': {\n locale: 'en-GB',\n countryCode: 'GB',\n flag: '&#x1F1EC;&#x1F1E7;',\n dialCode: '+44',\n nativeName: 'English (UK)',\n englishName: 'English (United Kingdom)',\n currencyCode: 'GBP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (캐나다)\n 'en-CA': {\n locale: 'en-CA',\n countryCode: 'CA',\n flag: '&#x1F1E8;&#x1F1E6;',\n dialCode: '+1',\n nativeName: 'English (Canada)',\n englishName: 'English (Canada)',\n currencyCode: 'CAD',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 영어 (호주)\n 'en-AU': {\n locale: 'en-AU',\n countryCode: 'AU',\n flag: '&#x1F1E6;&#x1F1FA;',\n dialCode: '+61',\n nativeName: 'English (Australia)',\n englishName: 'English (Australia)',\n currencyCode: 'AUD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (뉴질랜드)\n 'en-NZ': {\n locale: 'en-NZ',\n countryCode: 'NZ',\n flag: '&#x1F1F3;&#x1F1FF;',\n dialCode: '+64',\n nativeName: 'English (New Zealand)',\n englishName: 'English (New Zealand)',\n currencyCode: 'NZD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (멕시코)\n 'es-MX': {\n locale: 'es-MX',\n countryCode: 'MX',\n flag: '&#x1F1F2;&#x1F1FD;',\n dialCode: '+52',\n nativeName: 'Español (México)',\n englishName: 'Spanish (Mexico)',\n currencyCode: 'MXN',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (아르헨티나)\n 'es-AR': {\n locale: 'es-AR',\n countryCode: 'AR',\n flag: '&#x1F1E6;&#x1F1F7;',\n dialCode: '+54',\n nativeName: 'Español (Argentina)',\n englishName: 'Spanish (Argentina)',\n currencyCode: 'ARS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (콜롬비아)\n 'es-CO': {\n locale: 'es-CO',\n countryCode: 'CO',\n flag: '&#x1F1E8;&#x1F1F4;',\n dialCode: '+57',\n nativeName: 'Español (Colombia)',\n englishName: 'Spanish (Colombia)',\n currencyCode: 'COP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스웨덴어\n sv: {\n locale: 'sv',\n countryCode: 'SE',\n flag: '&#x1F1F8;&#x1F1EA;',\n dialCode: '+46',\n nativeName: 'Svenska',\n englishName: 'Swedish',\n currencyCode: 'SEK',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 노르웨이어\n no: {\n locale: 'no',\n countryCode: 'NO',\n flag: '&#x1F1F3;&#x1F1F4;',\n dialCode: '+47',\n nativeName: 'Norsk',\n englishName: 'Norwegian',\n currencyCode: 'NOK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 덴마크어\n da: {\n locale: 'da',\n countryCode: 'DK',\n flag: '&#x1F1E9;&#x1F1F0;',\n dialCode: '+45',\n nativeName: 'Dansk',\n englishName: 'Danish',\n currencyCode: 'DKK',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 핀란드어\n fi: {\n locale: 'fi',\n countryCode: 'FI',\n flag: '&#x1F1EB;&#x1F1EE;',\n dialCode: '+358',\n nativeName: 'Suomi',\n englishName: 'Finnish',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 우크라이나어\n uk: {\n locale: 'uk',\n countryCode: 'UA',\n flag: '&#x1F1FA;&#x1F1E6;',\n dialCode: '+380',\n nativeName: 'Українська',\n englishName: 'Ukrainian',\n currencyCode: 'UAH',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 체코어\n cs: {\n locale: 'cs',\n countryCode: 'CZ',\n flag: '&#x1F1E8;&#x1F1FF;',\n dialCode: '+420',\n nativeName: 'Čeština',\n englishName: 'Czech',\n currencyCode: 'CZK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 헝가리어\n hu: {\n locale: 'hu',\n countryCode: 'HU',\n flag: '&#x1F1ED;&#x1F1FA;',\n dialCode: '+36',\n nativeName: 'Magyar',\n englishName: 'Hungarian',\n currencyCode: 'HUF',\n dateFormat: 'YYYY.MM.DD.',\n },\n\n // 루마니아어\n ro: {\n locale: 'ro',\n countryCode: 'RO',\n flag: '&#x1F1F7;&#x1F1F4;',\n dialCode: '+40',\n nativeName: 'Română',\n englishName: 'Romanian',\n currencyCode: 'RON',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 불가리아어\n bg: {\n locale: 'bg',\n countryCode: 'BG',\n flag: '&#x1F1E7;&#x1F1EC;',\n dialCode: '+359',\n nativeName: 'Български',\n englishName: 'Bulgarian',\n currencyCode: 'BGN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 크로아티아어\n hr: {\n locale: 'hr',\n countryCode: 'HR',\n flag: '&#x1F1ED;&#x1F1F7;',\n dialCode: '+385',\n nativeName: 'Hrvatski',\n englishName: 'Croatian',\n currencyCode: 'HRK',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 세르비아어\n sr: {\n locale: 'sr',\n countryCode: 'RS',\n flag: '&#x1F1F7;&#x1F1F8;',\n dialCode: '+381',\n nativeName: 'Српски',\n englishName: 'Serbian',\n currencyCode: 'RSD',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 슬로바키아어\n sk: {\n locale: 'sk',\n countryCode: 'SK',\n flag: '&#x1F1F8;&#x1F1F0;',\n dialCode: '+421',\n nativeName: 'Slovenčina',\n englishName: 'Slovak',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 슬로베니아어\n sl: {\n locale: 'sl',\n countryCode: 'SI',\n flag: '&#x1F1F8;&#x1F1EE;',\n dialCode: '+386',\n nativeName: 'Slovenščina',\n englishName: 'Slovenian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 리투아니아어\n lt: {\n locale: 'lt',\n countryCode: 'LT',\n flag: '&#x1F1F1;&#x1F1F9;',\n dialCode: '+370',\n nativeName: 'Lietuvių',\n englishName: 'Lithuanian',\n currencyCode: 'EUR',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 라트비아어\n lv: {\n locale: 'lv',\n countryCode: 'LV',\n flag: '&#x1F1F1;&#x1F1FB;',\n dialCode: '+371',\n nativeName: 'Latviešu',\n englishName: 'Latvian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 에스토니아어\n et: {\n locale: 'et',\n countryCode: 'EE',\n flag: '&#x1F1EA;&#x1F1EA;',\n dialCode: '+372',\n nativeName: 'Eesti',\n englishName: 'Estonian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 그리스어\n el: {\n locale: 'el',\n countryCode: 'GR',\n flag: '&#x1F1EC;&#x1F1F7;',\n dialCode: '+30',\n nativeName: 'Ελληνικά',\n englishName: 'Greek',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 페르시아어\n fa: {\n locale: 'fa',\n countryCode: 'IR',\n flag: '&#x1F1EE;&#x1F1F7;',\n dialCode: '+98',\n nativeName: 'فارسی',\n englishName: 'Persian',\n rtl: true,\n currencyCode: 'IRR',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 히브리어\n he: {\n locale: 'he',\n countryCode: 'IL',\n flag: '&#x1F1EE;&#x1F1F1;',\n dialCode: '+972',\n nativeName: 'עברית',\n englishName: 'Hebrew',\n rtl: true,\n currencyCode: 'ILS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스와힐리어\n sw: {\n locale: 'sw',\n countryCode: 'KE',\n flag: '&#x1F1F0;&#x1F1EA;',\n dialCode: '+254',\n nativeName: 'Kiswahili',\n englishName: 'Swahili',\n currencyCode: 'KES',\n dateFormat: 'DD/MM/YYYY',\n },\n};\n\n/**\n * Locale 정보 가져오기\n *\n * @param locale - Locale 코드 (예: 'ko', 'en', 'ja')\n * @returns LocaleInfo 또는 undefined\n *\n * @example\n * ```typescript\n * const koInfo = getLocaleInfo('ko');\n * console.log(koInfo.flag); // 🇰🇷\n * console.log(koInfo.dialCode); // +82\n * ```\n */\nexport function getLocaleInfo(locale: string): LocaleInfo | undefined\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale];\n}\n\n/**\n * 시스템이 지원 가능한 모든 Locale 목록 가져오기\n *\n * @returns Locale 코드 배열 (50+ locales available)\n */\nexport function getAllLocales(): SupportedLocale[]\n{\n return Object.keys(LOCALE_INFO_MAP) as SupportedLocale[];\n}\n\n/**\n * @deprecated Use getAllLocales() instead\n */\nexport function getSupportedLocales(): SupportedLocale[]\n{\n return getAllLocales();\n}\n\n/**\n * 국기 이모지만 가져오기\n *\n * @param locale - Locale 코드\n * @returns 국기 이모지 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getFlag('ko'); // 🇰🇷\n * getFlag('en'); // 🇺🇸\n * ```\n */\nexport function getFlag(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.flag ?? '';\n}\n\n/**\n * 전화번호 국가 코드 가져오기\n *\n * @param locale - Locale 코드\n * @returns 전화번호 코드 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getDialCode('ko'); // +82\n * getDialCode('en'); // +1\n * ```\n */\nexport function getDialCode(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.dialCode ?? '';\n}\n\n/**\n * RTL (Right-to-Left) 여부 확인\n *\n * @param locale - Locale 코드\n * @returns RTL 여부\n *\n * @example\n * ```typescript\n * isRTL('ar'); // true (Arabic)\n * isRTL('ko'); // false (Korean)\n * ```\n */\nexport function isRTL(locale: string): boolean\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.rtl ?? false;\n}"],"mappings":";AAUA,SAAS,SAAS,eAAe;;;ACgCjC,SAAS,UAAU,KAAa,cAChC;AACI,SAAO,QAAQ,IAAI,GAAG,KAAK;AAC/B;AAKA,SAAS,cAAc,KAAa,cACpC;AACI,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,UAAU,UAAU,UAAU;AACzC;AAKA,SAAS,oBACT;AACI,QAAM,gBAAgB,UAAU,2BAA2B,IAAI;AAC/D,QAAM,sBAAsB,UAAU,8BAA8B,OAAO;AAC3E,QAAMA,yBAAwB,cAAc,oCAAoC,IAAI;AAEpF,QAAM,UAAU,oBACX,MAAM,GAAG,EACT,IAAI,YAAU,OAAO,KAAK,CAAC,EAC3B,OAAO,YAAU,OAAO,SAAS,CAAC;AAGvC,MAAI,CAAC,QAAQ,SAAS,aAAa,GACnC;AACI,YAAQ,QAAQ,aAAa;AAAA,EACjC;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA;AAAA,IAClB,uBAAAA;AAAA,EACJ;AACJ;AAKA,IAAI,gBAA2B,kBAAkB;AAgB1C,SAAS,eAChB;AACI,SAAO;AACX;;;AClGO,IAAM,oBAAoB;;;AFgBjC,eAAe,wBACf;AACI,MACA;AACI,UAAM,cAAc,MAAM,QAAQ;AAClC,UAAM,iBAAiB,YAAY,IAAI,iBAAiB;AAExD,QAAI,CAAC,gBACL;AACI,aAAO;AAAA,IACX;AAGA,UAAM,YAAY,eACb,MAAM,GAAG,EACT,IAAI,UACL;AACI,YAAM,CAAC,IAAI,IAAI,KAAK,MAAM,GAAG;AAC7B,aAAO,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK;AAAA,IACnC,CAAC;AAEL,UAAM,SAAS,aAAa;AAG5B,eAAW,QAAQ,WACnB;AACI,UAAI,OAAO,QAAQ,SAAS,IAAI,GAChC;AACI,eAAO;AAAA,MACX;AAAA,IACJ;AAEA,WAAO;AAAA,EACX,SACO,OACP;AAEI,WAAO;AAAA,EACX;AACJ;AA4CA,eAAsB,YACtB;AACI,QAAM,SAAS,aAAa;AAG5B,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,eAAe,YAAY,IAAI,iBAAiB,GAAG;AAEzD,MAAI,gBAAgB,OAAO,QAAQ,SAAS,YAAY,GACxD;AACI,WAAO;AAAA,EACX;AAGA,MAAI,OAAO,uBACX;AACI,UAAM,cAAc,MAAM,sBAAsB;AAChD,QAAI,aACJ;AACI,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,SAAO,OAAO;AAClB;AA6CA,eAAsB,UAAU,QAChC;AACI,QAAM,SAAS,aAAa;AAG5B,MAAI,CAAC,OAAO,QAAQ,SAAS,MAAM,GACnC;AACI,UAAM,IAAI;AAAA,MACN,uBAAuB,MAAM,wBAAwB,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,IAClF;AAAA,EACJ;AAEA,QAAM,cAAc,MAAM,QAAQ;AAElC,cAAY,IAAI,mBAAmB,QAAQ;AAAA,IACvC,MAAM;AAAA,IACN,QAAQ,KAAK,KAAK,KAAK;AAAA;AAAA,IACvB,UAAU;AAAA,EACd,CAAC;AACL;AA6CA,eAAsB,aACtB;AACI,QAAM,SAAS,aAAa;AAC5B,SAAO,OAAO;AAClB;","names":["detectBrowserLanguage"]}
package/dist/api.d.ts CHANGED
@@ -112,6 +112,8 @@ declare const cmsApi: {
112
112
  readonly getLabels: (options: {
113
113
  query?: GetLabelsQuery;
114
114
  }) => Promise<{
115
+ limit: number;
116
+ offset: number;
115
117
  labels: {
116
118
  defaultValue?: any;
117
119
  section: string;
@@ -124,8 +126,6 @@ declare const cmsApi: {
124
126
  createdAt: string;
125
127
  updatedAt: string;
126
128
  }[];
127
- limit: number;
128
- offset: number;
129
129
  total: number;
130
130
  }>;
131
131
  readonly createLabel: (options: {
@@ -202,9 +202,9 @@ declare const cmsApi: {
202
202
  params: PublishLabelParams;
203
203
  body: PublishLabelBody;
204
204
  }) => Promise<{
205
- version: number;
206
- labelId: number;
207
205
  success: boolean;
206
+ labelId: number;
207
+ version: number;
208
208
  message: string;
209
209
  } | {
210
210
  error: string;
@@ -224,22 +224,22 @@ declare const cmsApi: {
224
224
  updatedAt: string;
225
225
  };
226
226
  draft: {
227
- locale: string;
228
- version: null;
229
227
  id: number;
230
- value: any;
231
228
  createdAt: string;
232
229
  labelId: number;
230
+ version: null;
231
+ locale: string;
233
232
  breakpoint: string | null;
233
+ value: any;
234
234
  }[];
235
235
  published: {
236
- locale: string;
237
- version: number;
238
236
  id: number;
239
- value: any;
240
237
  createdAt: string;
241
238
  labelId: number;
239
+ version: number;
240
+ locale: string;
242
241
  breakpoint: string | null;
242
+ value: any;
243
243
  }[];
244
244
  status: "published" | "default-only" | "unpublished" | "modified";
245
245
  } | {
@@ -249,17 +249,17 @@ declare const cmsApi: {
249
249
  params: GetLabelVersionsParams;
250
250
  }) => Promise<{
251
251
  versions: {
252
+ notes: string | null;
253
+ publishedBy: string | null;
252
254
  version: number;
253
255
  publishedAt: string;
254
- publishedBy: string | null;
255
256
  values: {
256
- locale: string;
257
257
  id: number;
258
- value: any;
259
258
  createdAt: string;
259
+ locale: string;
260
260
  breakpoint: string | null;
261
+ value: any;
261
262
  }[];
262
- notes: string | null;
263
263
  }[];
264
264
  } | {
265
265
  error: string;
@@ -268,12 +268,12 @@ declare const cmsApi: {
268
268
  query?: GetPublishedCacheQuery;
269
269
  }) => Promise<{
270
270
  section: string;
271
+ version: number;
271
272
  locale: string;
273
+ publishedAt: string | null;
272
274
  content: {
273
275
  [x: string]: any;
274
276
  };
275
- version: number;
276
- publishedAt: string | null;
277
277
  }[] | {
278
278
  error: string;
279
279
  }>;
@@ -281,12 +281,12 @@ declare const cmsApi: {
281
281
  body: UpsertPublishedCacheBody;
282
282
  }) => Promise<{
283
283
  section: string;
284
+ version: number;
284
285
  locale: string;
286
+ publishedAt: string | null;
285
287
  content: {
286
288
  [x: string]: any;
287
289
  };
288
- version: number;
289
- publishedAt: string | null;
290
290
  } | {
291
291
  error: string;
292
292
  }>;
@@ -294,8 +294,8 @@ declare const cmsApi: {
294
294
  params: SaveValuesParams;
295
295
  body: SaveValuesBody;
296
296
  }) => Promise<{
297
- version: number | null;
298
297
  success: boolean;
298
+ version: number | null;
299
299
  saved: number;
300
300
  } | {
301
301
  error: string;
@@ -304,14 +304,14 @@ declare const cmsApi: {
304
304
  params: GetValuesParams;
305
305
  query?: GetValuesQuery;
306
306
  }) => Promise<{
307
- version: number;
308
307
  labelId: number;
308
+ version: number;
309
309
  values: {
310
- locale: string;
311
310
  id: number;
312
- value: any;
313
311
  createdAt: string;
312
+ locale: string;
314
313
  breakpoint: string | null;
314
+ value: any;
315
315
  }[];
316
316
  } | {
317
317
  error: string;
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SectionData, SectionAPI } from './server.js';
2
2
  import * as zustand from 'zustand';
3
- export { g as getLocale, b as getLocaleWithInfo, a as getLocales, c as getLocalesWithInfo, s as setLocale } from './actions-BpTAYuBA.js';
4
- export { L as LOCALE_COOKIE_KEY, d as LOCALE_INFO_MAP, e as LocaleInfo, S as SupportedLocale, c as getDialCode, b as getFlag, g as getLocaleInfo, a as getSupportedLocales, i as isRTL } from './locale.constants-BMBK70YM.js';
3
+ export { g as getLocale, b as getLocaleWithInfo, a as getLocales, c as getLocalesWithInfo, s as setLocale } from './actions-BEFWwQsh.js';
4
+ export { L as LOCALE_COOKIE_KEY, e as LOCALE_INFO_MAP, f as LocaleInfo, S as SupportedLocale, d as getDialCode, c as getFlag, g as getLocaleInfo, b as getSupportedLocales, i as isRTL } from './locale.constants-BNkSdNP1.js';
5
5
  import './index-Dh5FjWzR.js';
6
6
  import './server/repositories/index.js';
7
7
  import './server/entities/cms-labels.js';
package/dist/client.js CHANGED
@@ -665,13 +665,15 @@ function loadConfigFromEnv() {
665
665
  const defaultLocale = getEnvVar("SPFN_CMS_DEFAULT_LOCALE", "en");
666
666
  const supportedLocalesStr = getEnvVar("SPFN_CMS_SUPPORTED_LOCALES", "en,ko");
667
667
  const detectBrowserLanguage2 = getEnvBoolean("SPFN_CMS_DETECT_BROWSER_LANGUAGE", true);
668
- const supportedLocales = supportedLocalesStr.split(",").map((locale) => locale.trim()).filter((locale) => locale.length > 0);
669
- if (!supportedLocales.includes(defaultLocale)) {
670
- supportedLocales.unshift(defaultLocale);
668
+ const locales = supportedLocalesStr.split(",").map((locale) => locale.trim()).filter((locale) => locale.length > 0);
669
+ if (!locales.includes(defaultLocale)) {
670
+ locales.unshift(defaultLocale);
671
671
  }
672
672
  return {
673
673
  defaultLocale,
674
- supportedLocales,
674
+ locales,
675
+ supportedLocales: locales,
676
+ // backward compatibility
675
677
  detectBrowserLanguage: detectBrowserLanguage2
676
678
  };
677
679
  }
@@ -1218,9 +1220,12 @@ var LOCALE_INFO_MAP = {
1218
1220
  function getLocaleInfo(locale) {
1219
1221
  return LOCALE_INFO_MAP[locale];
1220
1222
  }
1221
- function getSupportedLocales() {
1223
+ function getAllLocales() {
1222
1224
  return Object.keys(LOCALE_INFO_MAP);
1223
1225
  }
1226
+ function getSupportedLocales() {
1227
+ return getAllLocales();
1228
+ }
1224
1229
  function getFlag(locale) {
1225
1230
  return LOCALE_INFO_MAP[locale]?.flag ?? "";
1226
1231
  }
@@ -1245,7 +1250,7 @@ async function detectBrowserLanguage() {
1245
1250
  });
1246
1251
  const config = getCmsConfig();
1247
1252
  for (const lang of languages) {
1248
- if (config.supportedLocales.includes(lang)) {
1253
+ if (config.locales.includes(lang)) {
1249
1254
  return lang;
1250
1255
  }
1251
1256
  }
@@ -1258,7 +1263,7 @@ async function getLocale() {
1258
1263
  const config = getCmsConfig();
1259
1264
  const cookieStore = await cookies();
1260
1265
  const cookieLocale = cookieStore.get(LOCALE_COOKIE_KEY)?.value;
1261
- if (cookieLocale && config.supportedLocales.includes(cookieLocale)) {
1266
+ if (cookieLocale && config.locales.includes(cookieLocale)) {
1262
1267
  return cookieLocale;
1263
1268
  }
1264
1269
  if (config.detectBrowserLanguage) {
@@ -1271,9 +1276,9 @@ async function getLocale() {
1271
1276
  }
1272
1277
  async function setLocale(locale) {
1273
1278
  const config = getCmsConfig();
1274
- if (!config.supportedLocales.includes(locale)) {
1279
+ if (!config.locales.includes(locale)) {
1275
1280
  throw new Error(
1276
- `Unsupported locale: ${locale}. Supported locales: ${config.supportedLocales.join(", ")}`
1281
+ `Unsupported locale: ${locale}. Supported locales: ${config.locales.join(", ")}`
1277
1282
  );
1278
1283
  }
1279
1284
  const cookieStore = await cookies();
@@ -1286,7 +1291,7 @@ async function setLocale(locale) {
1286
1291
  }
1287
1292
  async function getLocales() {
1288
1293
  const config = getCmsConfig();
1289
- return config.supportedLocales;
1294
+ return config.locales;
1290
1295
  }
1291
1296
  async function getLocaleWithInfo() {
1292
1297
  const locale = await getLocale();
@@ -1295,8 +1300,8 @@ async function getLocaleWithInfo() {
1295
1300
  }
1296
1301
  async function getLocalesWithInfo() {
1297
1302
  const config = getCmsConfig();
1298
- const supportedLocales = config.supportedLocales;
1299
- return supportedLocales.map((locale) => getLocaleInfo(locale)).filter((info) => info !== void 0);
1303
+ const locales = config.locales;
1304
+ return locales.map((locale) => getLocaleInfo(locale)).filter((info) => info !== void 0);
1300
1305
  }
1301
1306
  export {
1302
1307
  InitCms,