o2o-triple-pick/frontend/src/lib/i18n.ts

216 lines
8.0 KiB
TypeScript

// 경량 i18n — ?lang=en 쿼리로 한/영 전환. SSG 유지(서버에서 lang 주입).
import type { Team } from "./types";
export type Lang = "ko" | "en";
export function parseLang(v: unknown): Lang {
return v === "en" ? "en" : "ko";
}
// 팀 짧은 표기 (KO=한글, EN=영문 약식)
const TEAM_EN: Record<string, string> = {
KOR: "Korea",
CZE: "Czechia",
MEX: "Mexico",
RSA: "S. Africa",
};
export function teamShort(team: Team, lang: Lang): string {
return lang === "en" ? TEAM_EN[team.code] ?? team.name : team.shortName;
}
const DAYS: Record<Lang, string[]> = {
ko: ["일", "월", "화", "수", "목", "금", "토"],
en: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
};
const MONTHS_EN = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
export function dayName(lang: Lang, dow: number): string {
return DAYS[lang][dow];
}
export function monthEn(m: number): string {
return MONTHS_EN[m - 1];
}
// roundLabel 안의 한국어 토막 번역
export function roundLabel(label: string, lang: Lang): string {
if (lang === "ko") return label;
return label.replace("개막전", "Opener");
}
type Dict = {
heroPill: string;
back: string;
share: string;
shareCopied: string;
introLead: string;
prizeLine1: string;
prizeLine2: string;
schedTitle: string;
schedGuide: string;
phase: { open: string; scheduled: string; locked: string; finished: string };
aiPicks: string;
draw: string;
joined: (n: string) => string;
aiBattle: string;
yourPickLabel: string;
yourChoice: string;
win: string;
drawLabel: string;
winProb: (team: string) => string;
drawOdds: string;
ctaBeat: string;
ctaResult: string;
ctaLocked: string;
ctaOpens: (d: string) => string;
emailPh: string;
notify: string;
submit: string;
myPick: string;
sameAI: (models: string, exact: boolean) => string;
soloPick: string;
shareThis: string;
copyLink: string;
copied: string;
goldDesc: string;
goldBtn: string;
crowdSub: string;
finalResult: string;
hit: string;
miss: string;
matchEnded: string;
cdUntil: string;
cdClosed: string;
final: string;
group: (g: string) => string;
footerNotOfficial: string;
footerDisc1: string;
footerDisc2: string;
footerDisc3: string;
hook: (a: string, b: string) => string;
adLabel: string;
ado2Tagline: string;
ado2Sub: string;
ado2Cta: string;
};
export const DICT: Record<Lang, Dict> = {
ko: {
heroPill: "AI와 함께하는 2026 월드컵 승부예측 챌린지",
back: "← 전체 일정",
share: "투표 공유하기",
shareCopied: "링크 복사됨 ✓",
introLead: "GPT · Claude · Gemini가 매 경기를\n서로 다르게 예측합니다.",
prizeLine1: "가장 많이 참여하고, 가장 정확하게 예측한",
prizeLine2: "최종 우승자에게 100만원 상금",
schedTitle: "전체 일정",
schedGuide: "투표하고 싶은 경기를 선택해주세요",
phase: { open: "투표 중", scheduled: "오픈 예정", locked: "투표 마감", finished: "종료" },
aiPicks: "AI 픽",
draw: "무",
joined: (n) => `${n}명 참여`,
aiBattle: "AI의 예측 대결",
yourPickLabel: "Your Pick · 내 예측",
yourChoice: "당신의 선택",
win: "승",
drawLabel: "무승부",
winProb: (t) => `${t} 승리 확률`,
drawOdds: "무승부 가능성",
ctaBeat: "AI보다 잘 맞히기",
ctaResult: "결과 보기",
ctaLocked: "예측 마감 (경기 시작)",
ctaOpens: (d) => `투표 오픈 ${d}`,
emailPh: "이메일 (결과·다음 경기 알림)",
notify: "경기 결과가 나오면 알려주세요. 다음 경기·100만원 챌린지 소식도 받겠습니다.",
submit: "제출하고 AI와 겨루기",
myPick: "내 예측",
sameAI: (m, exact) =>
`당신은 ${m}와 같은 선택입니다.${exact ? " 스코어까지 일치!" : ""} 나머지 AI는 생각이 다릅니다.`,
soloPick: "당신은 AI 셋 모두와 다른 선택! 소신 픽 🔥",
shareThis: "이 경기 공유하기",
copyLink: "링크 복사",
copied: "복사됨 ✓",
goldDesc: "경기마다 승패·스코어를 맞혀 포인트를 쌓고, 누적 1위에게 최종 100만원",
goldBtn: "100만 원에\n도전하기",
crowdSub: "| 참여자들의 선택",
finalResult: "최종 결과",
hit: "적중 ✓",
miss: "빗나감",
matchEnded: "경기 종료 · 최종 결과",
cdUntil: "투표 종료까지",
cdClosed: "투표가 마감되었습니다",
final: "Final",
group: (g) => `Group ${g}`,
footerNotOfficial:
"Independent AI prediction game — not affiliated with, endorsed by, or sponsored by FIFA or the official World Cup.",
footerDisc1: "FIFA 및 공식 월드컵과 무관한 독립 AI 예측 게임입니다 (제휴·후원·운영 아님).",
footerDisc2: "스포츠 분석·엔터테인먼트 목적의 예측 게임이며 베팅·도박을 권유하지 않습니다. AI 예측은 실제 결과를 보장하지 않습니다.",
footerDisc3: "100만 원 이벤트는 무료 참여형 챌린지입니다. 지급·동점 처리 조건은 별도 약관에 따릅니다. 이메일은 결과·이벤트 알림 목적으로만 사용됩니다.",
hook: (a, b) => `${a} vs ${b}, AI의 선택은 갈렸다`,
adLabel: "광고",
ado2Tagline: "AI 마케팅 자동화 플랫폼",
ado2Sub: "AI Marketing Automation Platform →",
ado2Cta: "사용해보기",
},
en: {
heroPill: "AI-powered World Cup 2026 match predictions",
back: "← All matches",
share: "Share vote",
shareCopied: "Link copied ✓",
introLead: "GPT · Claude · Gemini predict\nevery match differently.",
prizeLine1: "The most active, most accurate",
prizeLine2: "overall winner takes ₩1,000,000",
schedTitle: "All Matches",
schedGuide: "Pick a match to predict",
phase: { open: "Voting", scheduled: "Opens soon", locked: "Closed", finished: "Ended" },
aiPicks: "AI picks",
draw: "Draw",
joined: (n) => `${n} joined`,
aiBattle: "AI Prediction Battle",
yourPickLabel: "Your Pick",
yourChoice: "Your Choice",
win: "Win",
drawLabel: "Draw",
winProb: (t) => `${t} win prob.`,
drawOdds: "Draw odds",
ctaBeat: "Beat the AI",
ctaResult: "View result",
ctaLocked: "Closed (kickoff)",
ctaOpens: (d) => `Opens ${d}`,
emailPh: "Email (result & next-match alerts)",
notify: "Notify me when the result is out. I'll also get next-match & ₩1,000,000 challenge news.",
submit: "Submit & face the AI",
myPick: "My Pick",
sameAI: (m, exact) =>
`You matched ${m}.${exact ? " Exact score too!" : ""} The other AIs disagree.`,
soloPick: "You disagree with all three AIs — bold pick 🔥",
shareThis: "Share this match",
copyLink: "Copy link",
copied: "Copied ✓",
goldDesc: "Earn points each match by nailing the result & score — the overall #1 wins ₩1,000,000.",
goldBtn: "Go for\n₩1,000,000",
crowdSub: "| What the crowd picked",
finalResult: "Final result",
hit: "Hit ✓",
miss: "Miss",
matchEnded: "Match ended · Final",
cdUntil: "Voting closes in",
cdClosed: "Voting closed",
final: "Final",
group: (g) => `Group ${g}`,
footerNotOfficial:
"Independent AI prediction game — not affiliated with, endorsed by, or sponsored by FIFA or the official World Cup.",
footerDisc1: "A fan-run prediction game using public match schedules; all picks are independent.",
footerDisc2: "A sports-analysis & entertainment prediction game. No betting or gambling. AI predictions do not guarantee real outcomes.",
footerDisc3: "The ₩1,000,000 event is a free-to-enter challenge. Payout & tie-break terms follow separate rules. Email is used only for result & event alerts.",
hook: (a, b) => `AI is split on ${a} vs ${b}`,
adLabel: "AD",
ado2Tagline: "AI Marketing Automation Platform",
ado2Sub: "Visit ado2.o2osolution.ai →",
ado2Cta: "Try it",
},
};
export function dict(lang: Lang): Dict {
return DICT[lang];
}