diff --git a/app/compare/page.tsx b/app/compare/page.tsx index 044479d..0aee1a5 100644 --- a/app/compare/page.tsx +++ b/app/compare/page.tsx @@ -1,17 +1,25 @@ import type { Metadata } from "next"; import Link from "next/link"; -import { COMPARISONS, CATEGORY_LABELS } from "@/lib/compare-pages/comparisons"; +import { COMPARISONS } from "@/lib/compare-pages/comparisons"; import type { ComparisonDef } from "@/lib/compare-pages/comparisons"; - -export const metadata: Metadata = { - title: "제품 비교 모음 — Axis", - description: - "노트북·스마트폰·이어폰·태블릿 인기 비교 모음. 공식 스펙과 실사용 기준으로 어떤 걸 살지 바로 알려드립니다.", -}; +import { localizedComparisonTitle } from "@/lib/compare-pages/localized-title"; +import { getDictionary } from "@/lib/i18n"; +import { getLocale } from "@/lib/i18n/server"; const CATEGORIES: ComparisonDef["category"][] = ["laptop", "smartphone", "earphones", "tablet"]; -export default function ComparePage() { +export async function generateMetadata(): Promise { + const locale = await getLocale(); + const t = getDictionary(locale); + return { + title: t.home.compareIndex.metaTitle, + description: t.home.compareIndex.metaDescription, + }; +} + +export default async function ComparePage() { + const locale = await getLocale(); + const t = getDictionary(locale); const byCategory = Object.fromEntries( CATEGORIES.map((cat) => [cat, COMPARISONS.filter((c) => c.category === cat)]) ) as Record; @@ -25,21 +33,21 @@ export default function ComparePage() {
-

제품 비교 모음

-

- 공식 스펙 + AI 분석으로 뭘 살지 바로 결론 내드립니다. -

+

{t.home.compareIndex.title}

+

{t.home.compareIndex.sub}

{CATEGORIES.map((cat) => (
-

{CATEGORY_LABELS[cat]}

+

{t.home.categoryLabels[cat]}

    {byCategory[cat].map((c) => (
  • - {c.title} -

    {c.description.slice(0, 72)}...

    + {localizedComparisonTitle(c, locale)} + {locale === "ko" ? ( +

    {c.description.slice(0, 72)}...

    + ) : null}
  • ))} @@ -48,9 +56,9 @@ export default function ComparePage() { ))}
    -

    목록에 없는 제품? 직접 비교해보세요.

    +

    {t.home.compareIndex.ctaCopy}

    - 직접 비교하기 → + {t.home.compareIndex.cta}
    diff --git a/app/loading.tsx b/app/loading.tsx index 246195a..71eca86 100644 --- a/app/loading.tsx +++ b/app/loading.tsx @@ -1,7 +1,13 @@ -export default function Loading() { +import { getLocale } from "@/lib/i18n/server"; +import { getDictionary } from "@/lib/i18n"; + +export default async function Loading() { + const locale = await getLocale(); + const t = getDictionary(locale); + return (
    -

    불러오는 중...

    +

    {t.error.loading}

    ); } diff --git a/app/page.tsx b/app/page.tsx index 7e28be7..7fa2512 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -12,6 +12,7 @@ import { COMPARISONS } from "@/lib/compare-pages/comparisons"; import { createServiceClientSafe } from "@/lib/supabase-server"; import { aggregatePopularQueries } from "@/lib/popular-queries"; import { coverageNoteYear } from "@/lib/specs/coverage"; +import { localizedComparisonTitle } from "@/lib/compare-pages/localized-title"; type PopularQuery = { query: string; count: number }; @@ -41,9 +42,9 @@ export default async function Home() { const locale = await getLocale(); const t = getDictionary(locale); - // 인기 비교: DB 기반 + 부족하면 하드코딩 페이지로 채움 - const popularQueries = await getPopularQueries(10); - const hasRealData = popularQueries.length >= 3; + // 인기 비교: KO만 DB 실데이터를 쓰고, en/ja는 로케일 타이틀 정적 목록을 쓴다. + const popularQueries = locale === "ko" ? await getPopularQueries(10) : []; + const hasRealData = locale === "ko" && popularQueries.length >= 3; return (
    @@ -88,7 +89,7 @@ export default async function Home() {
    -

    Decision method

    +

    {t.home.methodKicker}

    {t.home.methodTitle}

    {t.home.methodSubEm}{" "} @@ -127,7 +128,7 @@ export default async function Home() {

  • {i + 1} - {c.title} + {localizedComparisonTitle(c, locale)}
  • diff --git a/lib/compare-pages/localized-title.ts b/lib/compare-pages/localized-title.ts new file mode 100644 index 0000000..9b1f6f8 --- /dev/null +++ b/lib/compare-pages/localized-title.ts @@ -0,0 +1,150 @@ +import type { Locale } from "@/lib/i18n"; +import type { ComparisonDef } from "./comparisons"; + +/** Locale-facing titles for curated comparison pages. Korean keeps the source title. */ +const TITLE_I18N: Record, string>>> = { + "macbook-air-m4-vs-macbook-pro-m4": { + en: "MacBook Air M4 vs MacBook Pro M4", + ja: "MacBook Air M4 vs MacBook Pro M4", + }, + "macbook-air-m3-vs-macbook-pro-m3": { + en: "MacBook Air M3 vs MacBook Pro M3", + ja: "MacBook Air M3 vs MacBook Pro M3", + }, + "macbook-air-13-vs-macbook-air-15": { + en: "MacBook Air 13-inch vs 15-inch", + ja: "MacBook Air 13インチ vs 15インチ", + }, + "macbook-air-m3-vs-lg-gram-16": { + en: "MacBook Air M3 vs LG gram 16", + ja: "MacBook Air M3 vs LG gram 16", + }, + "macbook-air-m3-vs-galaxy-book4-pro": { + en: "MacBook Air M3 vs Galaxy Book4 Pro", + ja: "MacBook Air M3 vs Galaxy Book4 Pro", + }, + "lg-gram-16-vs-galaxy-book4-pro": { + en: "LG gram 16 vs Galaxy Book4 Pro", + ja: "LG gram 16 vs Galaxy Book4 Pro", + }, + "macbook-air-m4-vs-lg-gram-16": { + en: "MacBook Air M4 vs LG gram 16", + ja: "MacBook Air M4 vs LG gram 16", + }, + "macbook-air-m4-vs-galaxy-book5-pro": { + en: "MacBook Air M4 vs Galaxy Book5 Pro", + ja: "MacBook Air M4 vs Galaxy Book5 Pro", + }, + "macbook-air-m4-vs-lg-gram-pro-16": { + en: "MacBook Air M4 vs LG gram Pro 16", + ja: "MacBook Air M4 vs LG gram Pro 16", + }, + "macbook-air-m3-vs-macbook-air-m4": { + en: "MacBook Air M3 vs M4 — upgrade value", + ja: "MacBook Air M3 vs M4 — アップグレード価値", + }, + "macbook-air-m2-vs-macbook-air-m3": { + en: "MacBook Air M2 vs M3 — used vs new", + ja: "MacBook Air M2 vs M3 — 中古 vs 新型", + }, + "lg-gram-pro-16-vs-galaxy-book5-pro": { + en: "LG gram Pro 16 vs Galaxy Book5 Pro", + ja: "LG gram Pro 16 vs Galaxy Book5 Pro", + }, + "lg-gram-16-vs-lg-gram-pro-16": { + en: "LG gram 16 vs gram Pro 16", + ja: "LG gram 16 vs gram Pro 16", + }, + "galaxy-book5-pro-vs-galaxy-book4-pro": { + en: "Galaxy Book5 Pro vs Book4 Pro — one-year gap", + ja: "Galaxy Book5 Pro vs Book4 Pro — 1年差の価値", + }, + "iphone-16-vs-galaxy-s25": { + en: "iPhone 16 vs Galaxy S25", + ja: "iPhone 16 vs Galaxy S25", + }, + "iphone-16-pro-vs-galaxy-s25-ultra": { + en: "iPhone 16 Pro vs Galaxy S25 Ultra", + ja: "iPhone 16 Pro vs Galaxy S25 Ultra", + }, + "iphone-16-vs-iphone-16-pro": { + en: "iPhone 16 vs iPhone 16 Pro", + ja: "iPhone 16 vs iPhone 16 Pro", + }, + "galaxy-s25-vs-s25-ultra": { + en: "Galaxy S25 vs S25 Ultra", + ja: "Galaxy S25 vs S25 Ultra", + }, + "iphone-15-vs-iphone-16": { + en: "iPhone 15 vs iPhone 16 — worth upgrading?", + ja: "iPhone 15 vs iPhone 16 — アップグレード価値は?", + }, + "galaxy-s24-vs-s25": { + en: "Galaxy S24 vs S25 — reason to switch?", + ja: "Galaxy S24 vs S25 — 乗り換える理由は?", + }, + "iphone-16-pro-max-vs-galaxy-s25-ultra": { + en: "iPhone 16 Pro Max vs Galaxy S25 Ultra", + ja: "iPhone 16 Pro Max vs Galaxy S25 Ultra", + }, + "iphone-14-pro-vs-iphone-15-pro": { + en: "iPhone 14 Pro vs iPhone 15 Pro", + ja: "iPhone 14 Pro vs iPhone 15 Pro", + }, + "iphone-15-pro-vs-iphone-16-pro": { + en: "iPhone 15 Pro vs iPhone 16 Pro", + ja: "iPhone 15 Pro vs iPhone 16 Pro", + }, + "galaxy-s24-ultra-vs-s25-ultra": { + en: "Galaxy S24 Ultra vs S25 Ultra", + ja: "Galaxy S24 Ultra vs S25 Ultra", + }, + "airpods-pro-2-vs-galaxy-buds3-pro": { + en: "AirPods Pro 2 vs Galaxy Buds3 Pro", + ja: "AirPods Pro 2 vs Galaxy Buds3 Pro", + }, + "airpods-pro-2-vs-sony-wf-1000xm5": { + en: "AirPods Pro 2 vs Sony WF-1000XM5", + ja: "AirPods Pro 2 vs Sony WF-1000XM5", + }, + "sony-wh-1000xm5-vs-bose-qc-ultra": { + en: "Sony WH-1000XM5 vs Bose QC Ultra", + ja: "Sony WH-1000XM5 vs Bose QC Ultra", + }, + "airpods-4-vs-galaxy-buds3": { + en: "AirPods 4 vs Galaxy Buds3", + ja: "AirPods 4 vs Galaxy Buds3", + }, + "airpods-pro-2-vs-airpods-4": { + en: "AirPods Pro 2 vs AirPods 4 — need Pro?", + ja: "AirPods Pro 2 vs AirPods 4 — Proは必要?", + }, + "galaxy-buds3-pro-vs-sony-wf-1000xm5": { + en: "Galaxy Buds3 Pro vs Sony WF-1000XM5", + ja: "Galaxy Buds3 Pro vs Sony WF-1000XM5", + }, + "ipad-pro-m4-vs-galaxy-tab-s10-ultra": { + en: "iPad Pro M4 vs Galaxy Tab S10 Ultra", + ja: "iPad Pro M4 vs Galaxy Tab S10 Ultra", + }, + "ipad-air-m2-vs-galaxy-tab-s9-plus": { + en: "iPad Air M2 vs Galaxy Tab S9+", + ja: "iPad Air M2 vs Galaxy Tab S9+", + }, + "ipad-pro-m4-vs-ipad-air-m2": { + en: "iPad Pro M4 vs iPad Air M2 — need Pro?", + ja: "iPad Pro M4 vs iPad Air M2 — Proは必要?", + }, + "ipad-10-vs-galaxy-tab-s9-fe": { + en: "iPad 10th gen vs Galaxy Tab S9 FE", + ja: "iPad 第10世代 vs Galaxy Tab S9 FE", + }, +}; + +export function localizedComparisonTitle( + comparison: Pick, + locale: Locale +): string { + if (locale === "ko") return comparison.title; + return TITLE_I18N[comparison.slug]?.[locale] ?? comparison.title; +} diff --git a/lib/i18n/en.ts b/lib/i18n/en.ts index 1931525..7ea6cc4 100644 --- a/lib/i18n/en.ts +++ b/lib/i18n/en.ts @@ -34,6 +34,7 @@ export const en = { } ], methodTitle: "How Axis decides", + methodKicker: "Decision method", methodSubEm: "Not marketing copy —", methodSub: "Axis starts with verifiable information and your real-world situation.", features: [ @@ -69,6 +70,15 @@ export const en = { earphones: "Earphones & Headphones", tablet: "Tablets", }, + compareIndex: { + title: "Product comparisons", + metaTitle: "Product comparisons — Axis", + metaDescription: + "Popular laptop, smartphone, earphone, and tablet comparisons. Official specs and real-world fit help you decide what to buy.", + sub: "Official specs + AI analysis so you can decide what to buy.", + ctaCopy: "Looking for something not listed? Compare it yourself.", + cta: "Compare now →", + }, }, input: { diff --git a/lib/i18n/ja.ts b/lib/i18n/ja.ts index bb7ad2c..f461745 100644 --- a/lib/i18n/ja.ts +++ b/lib/i18n/ja.ts @@ -34,6 +34,7 @@ export const ja = { } ], methodTitle: "Axisの判断方法", + methodKicker: "判断の方法", methodSubEm: "広告文ではなく、", methodSub: "確認できる情報とあなたの状況を先に見ます。", features: [ @@ -69,6 +70,15 @@ export const ja = { earphones: "イヤホン・ヘッドホン", tablet: "タブレット", }, + compareIndex: { + title: "製品比較まとめ", + metaTitle: "製品比較まとめ — Axis", + metaDescription: + "ノートPC・スマホ・イヤホン・タブレットの人気比較まとめ。公式スペックと実用基準で買うものをすぐ判断できます。", + sub: "公式スペックとAI分析で、買うものをすぐ結論づけます。", + ctaCopy: "一覧にない製品? 自分で比較してみてください。", + cta: "自分で比較する →", + }, }, input: { diff --git a/lib/i18n/ko.ts b/lib/i18n/ko.ts index 7dd9e3c..fcdcae2 100644 --- a/lib/i18n/ko.ts +++ b/lib/i18n/ko.ts @@ -36,6 +36,7 @@ export const ko = { } ], methodTitle: "Axis가 판단하는 방식", + methodKicker: "결정 방식", methodSubEm: "광고 문구가 아니라,", methodSub: "확인 가능한 정보와 사용자의 상황을 먼저 봅니다.", features: [ @@ -72,6 +73,15 @@ export const ko = { earphones: "이어폰·헤드폰", tablet: "태블릿", }, + compareIndex: { + title: "제품 비교 모음", + metaTitle: "제품 비교 모음 — Axis", + metaDescription: + "노트북·스마트폰·이어폰·태블릿 인기 비교 모음. 공식 스펙과 실사용 기준으로 어떤 걸 살지 바로 알려드립니다.", + sub: "공식 스펙 + AI 분석으로 뭘 살지 바로 결론 내드립니다.", + ctaCopy: "목록에 없는 제품? 직접 비교해보세요.", + cta: "직접 비교하기 →", + }, }, // Input