랜딩 페이지: D3RO 브랜드 프로모션 사이트 + GitHub Pages 배포

- Vite + React 19 + Tailwind CSS
- 4가지 디자인 레퍼런스 통합 (CRT 스크린, 크로스헤어, 노이즈, 인스트루먼트)
- 섹션 9개: Hero, Features, HowItWorks, Privacy, Pricing, FAQ, CTA, Header, Footer
- 재사용 컴포넌트 8개 (Badge, Container, InstrumentCard, GlowButton 등)
- i18n 10개 국어 (en/ko/ja/zh/es/fr/de/pt/ru/vi)
- GitHub Pages 자동 배포 워크플로우
This commit is contained in:
Yun Chan 2026-04-05 23:55:13 +09:00
parent eb83682269
commit 5892125740
45 changed files with 6225 additions and 0 deletions

227
site/src/i18n/index.ts Normal file
View file

@ -0,0 +1,227 @@
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
import { createElement } from 'react'
import { en } from './locales/en'
import { ko } from './locales/ko'
import { ja } from './locales/ja'
import { zh } from './locales/zh'
import { es } from './locales/es'
import { fr } from './locales/fr'
import { de } from './locales/de'
import { pt } from './locales/pt'
import { ru } from './locales/ru'
import { vi } from './locales/vi'
// ── Types ──────────────────────────────────────────────
export type Locale = 'en' | 'ko' | 'ja' | 'zh' | 'es' | 'fr' | 'de' | 'pt' | 'ru' | 'vi'
export interface FeatureItem {
title: string
description: string
}
export interface PipelineStep {
label: string
title: string
description: string
detail: string
}
export interface PrivacyMetricItem {
label: string
value: string
}
export interface FaqItem {
q: string
a: string
}
export interface PricingFeatureRow {
feature: string
free: string | boolean
pro: string | boolean
proPlus: string | boolean
}
export interface Translations {
nav: {
features: string
pipeline: string
privacy: string
pricing: string
faq: string
download: string
}
hero: {
badge: string
title1: string
title2: string
title3: string
subtitle: string
downloadBtn: string
viewFeatures: string
dataProcessing: string
dataCloudTraffic: string
dataLatency: string
dataPrivacy: string
systemStatus: string
protocol: string
}
features: {
index: string
title: string
subtitle: string
items: [FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem]
}
pipeline: {
index: string
title: string
subtitle: string
steps: [PipelineStep, PipelineStep, PipelineStep, PipelineStep]
panelTitle: string
panelActive: string
panelLatency: string
sttReady: string
llmConnected: string
transcription: string
aiPolish: string
sampleInput: string
sampleOutput: string
}
privacy: {
index: string
title: string
subtitle: string
monitorTitle: string
allClear: string
metrics: [PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem, PrivacyMetricItem]
guarantees: [string, string, string, string, string, string]
}
pricing: {
index: string
title: string
subtitle: string
featureLabel: string
free: string
pro: string
proPlus: string
forever: string
oneTime: string
popular: string
downloadFree: string
getPro: string
getProPlus: string
taxNote: string
rows: [PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow, PricingFeatureRow]
}
faq: {
index: string
title: string
items: [FaqItem, FaqItem, FaqItem, FaqItem, FaqItem, FaqItem]
}
cta: {
title1: string
title2: string
subtitle1: string
subtitle2: string
downloadBtn: string
systemReq: string
}
footer: {
description: string
copyright: string
builtWith: string
}
}
// ── Locale metadata ────────────────────────────────────
export interface LocaleMeta {
code: Locale
label: string
nativeName: string
}
export const LOCALES: LocaleMeta[] = [
{ code: 'en', label: 'EN', nativeName: 'English' },
{ code: 'ko', label: 'KO', nativeName: '한국어' },
{ code: 'ja', label: 'JA', nativeName: '日本語' },
{ code: 'zh', label: 'ZH', nativeName: '中文' },
{ code: 'es', label: 'ES', nativeName: 'Espanol' },
{ code: 'fr', label: 'FR', nativeName: 'Francais' },
{ code: 'de', label: 'DE', nativeName: 'Deutsch' },
{ code: 'pt', label: 'PT', nativeName: 'Portugues' },
{ code: 'ru', label: 'RU', nativeName: 'Русский' },
{ code: 'vi', label: 'VI', nativeName: 'Tieng Viet' },
]
// ── Translations map ───────────────────────────────────
const translations: Record<Locale, Translations> = {
en, ko, ja, zh, es, fr, de, pt, ru, vi,
}
// ── Storage ────────────────────────────────────────────
const STORAGE_KEY = 'd3ro-locale'
function detectLocale(): Locale {
// 1. Check localStorage
try {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored && stored in translations) return stored as Locale
} catch { /* noop */ }
// 2. Check browser language
const browserLang = navigator.language.toLowerCase()
const exact = LOCALES.find((l) => browserLang === l.code || browserLang.startsWith(l.code + '-'))
if (exact) return exact.code
// 3. Fallback
return 'en'
}
function persistLocale(locale: Locale): void {
try {
localStorage.setItem(STORAGE_KEY, locale)
} catch { /* noop */ }
}
// ── Context ────────────────────────────────────────────
interface I18nContextValue {
locale: Locale
t: Translations
setLocale: (locale: Locale) => void
}
const I18nContext = createContext<I18nContextValue | null>(null)
export function useI18n(): I18nContextValue {
const ctx = useContext(I18nContext)
if (!ctx) throw new Error('useI18n must be used within I18nProvider')
return ctx
}
// ── Provider ───────────────────────────────────────────
interface I18nProviderProps {
children: ReactNode
}
export function I18nProvider({ children }: I18nProviderProps) {
const [locale, setLocaleState] = useState<Locale>(detectLocale)
const setLocale = useCallback((newLocale: Locale) => {
setLocaleState(newLocale)
persistLocale(newLocale)
document.documentElement.lang = newLocale
}, [])
useEffect(() => {
document.documentElement.lang = locale
}, [locale])
const value: I18nContextValue = {
locale,
t: translations[locale],
setLocale,
}
return createElement(I18nContext.Provider, { value }, children)
}