去年我們為一個遊艇經紀平臺進行開發,該平臺需要在地中海地區提供六種語言的房源列表。法國買家在戛納、義大利客戶在波爾圖切爾沃、希臘包船尋求者在雅典、土耳其碼頭運營商在博德魯姆、西班牙經紀人在帕爾馬德馬約卡島,以及分散在各地的英語使用者。初期看似「只是添加一個翻譯按鈕」的簡單需求,最後演變成我處理過最有趣的國際化 (i18n) 挑戰之一。以下是我在構建真正能夠轉換的多語言遊艇網站方面學到的一切。

目錄

為地中海經紀公司構建多語言遊艇網站

為什麼地中海遊艇經紀公司需要多語言網站

據聯合市場研究公司預測,地中海遊艇市場預計將在 2026 年達到 123 億美元。但大多數機構忽視的是:這個市場在語言上存在根本性的碎片化。一艘在摩納哥掛牌的 45 米貝內蒂遊艇需要被德國工業家搜索到(他用德語搜尋)、被沙烏地買家發現(他用阿拉伯語瀏覽)以及被英國退休人士找到(他用英語查看)。

我見過經紀公司因為網站僅提供英文內容而損失六位數的佣金。一位來自昂蒂布的經紀人告訴我,他最大的競爭對手只是因為他們的房源出現在法語 Google 搜索結果中就贏得了講法語的客戶。這不是技術問題——這是有技術解決方案的商業問題。

數據支持這一點:

市場細分 需要的語言 買家特徵
法國蔚藍海岸 FR, EN, RU, AR 歐洲高淨值人士、中東買家
義大利海岸 IT, EN, DE, FR 北歐包船客戶
希臘島嶼 EL, EN, DE, FR 包船為主、季節性旅遊
土耳其里維埃拉 TR, EN, DE, RU 預算有限的包船市場
巴利阿里群島 ES, EN, DE, FR 混合經紀和包船
克羅埃西亞海岸 HR, EN, DE, IT 新興市場、增長迅速

如果你只提供一兩種語言,就是在浪費金錢。就是這樣。

選擇合適的技術堆棧

對於遊艇經紀網站,你需要一個能處理兩種非常不同內容類型的堆棧:靜態行銷內容(關於頁面、服務描述、團隊簡歷)和動態房源數據(遊艇規格、定價、可用性、照片)。

我曾在 Next.js 和 Astro 上構建過這些,兩者根據需求都運作良好。如果需要大量互動功能——已保存搜索、比較工具、具有實時可用性的詢問表單——Next.js 是更好的選擇。如果網站主要是展示,行為動態較少,Astro 的島嶼架構開箱即用提供驚人的性能。

以下是堆棧在此特定用例中的比較:

功能 Next.js(App Router) Astro Remix
i18n 路由 內置中間件 手動或插件 手動
靜態生成 優異 優異 有限
動態房源列表 原生 SSR/ISR 按需端點 原生 SSR
CMS 整合 優異 優異 良好
邊緣渲染 Vercel Edge、Cloudflare Cloudflare、Netlify Cloudflare
翻譯庫 next-intl、next-i18next astro-i18n、paraglide remix-i18next
構建時間(500 個房源 × 6 種語言) ~4 分鐘(使用 ISR) ~8 分鐘(完整靜態) N/A (SSR)

對於 headless CMS 層,我強烈建議將房源數據與行銷內容分開。使用專用的遊艇管理系統(如 Yatco API、NauticEd 或自訂 Supabase 後端)處理房源數據,使用 Sanity 或 Contentful 之類的 headless CMS 處理其他一切。

為什麼 Headless 在這裡很重要

遊艇數據很奇怪。你有以米或英尺表示的規格(取決於觀眾)、以歐元或美元表示的價格、不斷更新的引擎小時數,以及每日變化的可用性日曆。試圖在傳統 CMS 內管理所有這些是一場噩夢。headless 方法讓你可以從專用 API 拉取房源數據,從 CMS 拉取行銷內容,然後在構建時或請求時將它們組合起來。

多語言遊艇房源列表的 URL 策略

這是大多數項目早期出錯的地方。多語言網站的 URL 結構是最難以後扭轉的決定之一。有三種方法:

子目錄模式(推薦)

https://yachtbroker.com/en/yachts/benetti-45m-2022
https://yachtbroker.com/fr/yachts/benetti-45m-2022
https://yachtbroker.com/de/yachten/benetti-45m-2022

這是我對 90% 的遊艇經紀公司推薦的做法。單一域名、共享域名權威、易於使用 Next.js 中間件或 Astro 內置 i18n 路由實現。

子域名模式

https://en.yachtbroker.com/yachts/benetti-45m-2022
https://fr.yachtbroker.com/yachts/benetti-45m-2022

一些較大的經紀公司出於組織原因更喜歡這種方式。每個子域名可以獨立部署。但你會失去合併的域名權威,需要管理更多基礎設施。

國家代碼頂級域名模式

https://yachtbroker.fr/yachts/benetti-45m-2022
https://yachtbroker.de/yachten/benetti-45m-2022

只有在每個國家都有單獨法律實體時才有意義。昂貴、複雜,除非你是 Burgess 或 Fraser 級別的運營,否則幾乎不值得。

段落翻譯

這裡有一個細節會讓人困惑:你應該翻譯 URL 段落嗎?對於遊艇名稱,不應該——保持一致。「Benetti Oasis 40M」在每種語言中都叫這個名字。但類別路徑呢?是的,翻譯那些。

// next.config.js - Next.js i18n 路由
const nextConfig = {
  i18n: {
    locales: ['en', 'fr', 'de', 'it', 'es', 'el'],
    defaultLocale: 'en',
    localeDetection: true,
  },
};

對於 Next.js App Router 中使用 next-intl 的翻譯路徑:

// src/navigation.ts
import { createLocalizedPathnameNavigation } from 'next-intl/navigation';

export const localePrefix = 'always';

export const pathnames = {
  '/yachts': {
    en: '/yachts',
    fr: '/yachts',
    de: '/yachten',
    it: '/yacht',
    es: '/yates',
    el: '/skafi',
  },
  '/yachts/[slug]': {
    en: '/yachts/[slug]',
    fr: '/yachts/[slug]',
    de: '/yachten/[slug]',
    it: '/yacht/[slug]',
    es: '/yates/[slug]',
    el: '/skafi/[slug]',
  },
};

export const { Link, redirect, usePathname, useRouter } =
  createLocalizedPathnameNavigation({ locales, localePrefix, pathnames });

為地中海經紀公司構建多語言遊艇網站 - 架構

翻譯遊艇房源數據

這是核心挑戰。遊艇房源列表有三種內容類型,每種都需要不同的翻譯方法:

1. 結構化數據(不翻譯,本地化)

長度、寬度、吃水、引擎功率等規格——這些不需要翻譯。它們需要本地化。向歐洲人顯示米,向美國人顯示英尺。向某些市場顯示千瓦,向其他市場顯示馬力。

// utils/localize-specs.ts
const UNIT_PREFERENCES: Record<string, UnitSystem> = {
  en: 'imperial',
  'en-GB': 'metric', // 英國市場使用米作為遊艇單位
  fr: 'metric',
  de: 'metric',
  it: 'metric',
  es: 'metric',
  el: 'metric',
};

export function localizeLength(meters: number, locale: string): string {
  const system = UNIT_PREFERENCES[locale] || 'metric';
  if (system === 'imperial') {
    const feet = meters * 3.28084;
    return `${feet.toFixed(0)} ft`;
  }
  return `${meters.toFixed(1)} m`;
}

2. 枚舉欄位(使用翻譯鍵)

船體類型、燃料類型、遊艇類別——這些是固定選項,應該使用翻譯鍵,而不是自由文本翻譯。

// messages/en.json
{
  "yacht": {
    "hullType": {
      "monohull": "Monohull",
      "catamaran": "Catamaran",
      "trimaran": "Trimaran"
    },
    "fuelType": {
      "diesel": "Diesel",
      "electric": "Electric",
      "hybrid": "Hybrid"
    }
  }
}
// messages/fr.json
{
  "yacht": {
    "hullType": {
      "monohull": "Monocoque",
      "catamaran": "Catamaran",
      "trimaran": "Trimaran"
    },
    "fuelType": {
      "diesel": "Diesel",
      "electric": "Électrique",
      "hybrid": "Hybride"
    }
  }
}

3. 自由文本描述(困難的部分)

遊艇描述是行銷文案。它們由經紀人撰寫——通常用英語或法語——充滿行業術語、情感語言和具體聲稱。僅機器翻譯不足以應對 500 萬歐元的房源。

以下是我推薦的方法:

  1. 在 CMS/數據庫中存儲原始語言描述
  2. 使用 AI 翻譯作為第一步 — GPT-4o 或 Claude 在 2025 年處理遊艇術語的表現出人意料地好
  3. 為高於價格閾值的房源列表標記進行人工審查(比如 100 萬歐元以上)
  4. 快取翻譯的描述,這樣你就不會為每次請求的重新翻譯付費
// services/translate-listing.ts
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

export async function translateDescription(
  text: string,
  sourceLang: string,
  targetLang: string
): Promise<string> {
  const cached = await getFromCache(text, targetLang);
  if (cached) return cached;

  const { text: translated } = await generateText({
    model: openai('gpt-4o'),
    system: `You are a professional yacht broker translator. 
      Translate yacht listing descriptions from ${sourceLang} to ${targetLang}. 
      Preserve technical terminology. Maintain the luxury marketing tone. 
      Keep brand names, model names, and proper nouns unchanged.`,
    prompt: text,
  });

  await saveToCache(text, targetLang, translated);
  return translated;
}

這種方法的成本微乎其微。使用 GPT-4o 翻譯一份 500 字的遊艇描述成本大約 $0.01-0.02。即使是 500 個房源 × 6 種語言,你也只需花費約 $30-60 的初始翻譯費用。高級房源的人工審查增加了成本,但當單一遊艇銷售能產生 $50K-200K 的佣金時,這完全值得。

i18n 實現模式

讓我走過我使用 Next.js App Router 和 next-intl 的實際實現模式,因為這是大多數 headless CMS 項目 使用的堆棧。

項目結構

src/
├── app/
│   └── [locale]/
│       ├── layout.tsx
│       ├── page.tsx
│       └── yachts/
│           ├── page.tsx
│           └── [slug]/
│               └── page.tsx
├── messages/
│   ├── en.json
│   ├── fr.json
│   ├── de.json
│   ├── it.json
│   ├── es.json
│   └── el.json
├── middleware.ts
└── i18n.ts

區域設置偵測中間件

// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { locales, localePrefix, pathnames } from './navigation';

export default createMiddleware({
  locales,
  localePrefix,
  pathnames,
  defaultLocale: 'en',
  localeDetection: true,
});

export const config = {
  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
};

帶有翻譯的遊艇房源頁面

// app/[locale]/yachts/[slug]/page.tsx
import { useTranslations } from 'next-intl';
import { getYachtBySlug } from '@/lib/yachts';
import { localizeLength, localizePrice } from '@/utils/localize';

export async function generateMetadata({ params: { locale, slug } }) {
  const yacht = await getYachtBySlug(slug);
  const t = await getTranslations({ locale, namespace: 'yacht' });
  
  return {
    title: `${yacht.name} — ${localizeLength(yacht.lengthMeters, locale)} ${t('forSale')}`,
    alternates: {
      languages: {
        'en': `/en/yachts/${slug}`,
        'fr': `/fr/yachts/${slug}`,
        'de': `/de/yachten/${slug}`,
        'it': `/it/yacht/${slug}`,
        'es': `/es/yates/${slug}`,
        'el': `/el/skafi/${slug}`,
      },
    },
  };
}

export default async function YachtPage({ params: { locale, slug } }) {
  const yacht = await getYachtBySlug(slug);
  const t = useTranslations('yacht');
  const description = await getTranslatedDescription(yacht.id, locale);

  return (
    <article>
      <h1>{yacht.name}</h1>
      <dl>
        <dt>{t('specs.length')}</dt>
        <dd>{localizeLength(yacht.lengthMeters, locale)}</dd>
        <dt>{t('specs.year')}</dt>
        <dd>{yacht.year}</dd>
        <dt>{t('specs.price')}</dt>
        <dd>{localizePrice(yacht.priceEur, locale)}</dd>
        <dt>{t('specs.hullType')}</dt>
        <dd>{t(`hullType.${yacht.hullType}`)}</dd>
      </dl>
      <div dangerouslySetInnerHTML={{ __html: description }} />
    </article>
  );
}

處理貨幣和單位本地化

地中海遊艇定價幾乎總是以歐元列出,但來自不同市場的買家期望以當地貨幣看到參考價格。以下是我處理它的方式:

// utils/localize-price.ts
const CURRENCY_BY_LOCALE: Record<string, string> = {
  en: 'EUR',      // 國際英語在地中海市場預設為歐元
  'en-US': 'USD',
  fr: 'EUR',
  de: 'EUR',
  it: 'EUR',
  es: 'EUR',
  el: 'EUR',
  tr: 'EUR',      // 土耳其市場仍以歐元計價
  ar: 'USD',      // 中東買家更喜歡美元
  ru: 'EUR',
};

export function localizePrice(
  priceEur: number,
  locale: string,
  exchangeRates?: Record<string, number>
): string {
  const currency = CURRENCY_BY_LOCALE[locale] || 'EUR';
  let amount = priceEur;

  if (currency !== 'EUR' && exchangeRates) {
    amount = priceEur * (exchangeRates[currency] || 1);
  }

  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
    maximumFractionDigits: 0,
  }).format(amount);
}

重要警告:在顯示轉換後的價格時,始終顯示「歐元價格」或等效免責聲明。遊艇銷售合同以特定貨幣計價,未加上下文顯示轉換後的價格可能會造成法律問題。

多語言遊艇網站的 SEO

這是真正獲得回報的地方。適當的多語言 SEO 意味著你的 Azimut 68 房源會在慕尼黑某人搜索「Azimut 68 kaufen」時顯示,也會在巴黎某人搜索「Azimut 68 à vendre」時顯示。

hreflang 標籤

這些是必不可少的。每個頁面都需要 hreflang 標籤指向所有語言版本:

<link rel="alternate" hreflang="en" href="https://broker.com/en/yachts/azimut-68-2023" />
<link rel="alternate" hreflang="fr" href="https://broker.com/fr/yachts/azimut-68-2023" />
<link rel="alternate" hreflang="de" href="https://broker.com/de/yachten/azimut-68-2023" />
<link rel="alternate" hreflang="x-default" href="https://broker.com/en/yachts/azimut-68-2023" />

每種語言的結構化數據

對每個語言版本使用本地化描述的 Product 架構。Google 明確支持特定語言的結構化數據,並幫助你的房源在不同 Google 域名的豐富結果中顯示。

網站地圖策略

為每種語言生成單獨的網站地圖,並從網站地圖索引中引用它們:

<!-- sitemap-index.xml -->
<sitemapindex>
  <sitemap><loc>https://broker.com/sitemap-en.xml</loc></sitemap>
  <sitemap><loc>https://broker.com/sitemap-fr.xml</loc></sitemap>
  <sitemap><loc>https://broker.com/sitemap-de.xml</loc></sitemap>
</sitemapindex>

性能考量

包含 30 多張高解析度照片、翻譯內容和本地化規格的遊艇房源頁面會很快變得沉重。以下是重要的:

  • ISR(增量靜態再生成):每 60 分鐘重新生成房源列表頁面。遊艇房源不會逐秒變化,但定價和可用性可能每天改變。
  • 翻譯快取:永遠不要翻譯相同的描述兩次。使用 Redis 或甚至簡單的數據庫表來快取翻譯。
  • 圖像優化:這通常是最大的勝利。單一遊艇相冊可能包含 2GB 的源圖像。使用 Next.js Image 或具有自動格式協商的 CDN(WebP/AVIF)。
  • 按區域設置的資源包分割:不要為英文使用者載入法文翻譯。next-intlparaglide 都會自動處理這個問題。

在最近的一個項目中,這些優化將我們在所有區域設置中的最大內容繪製從 4.2 秒降低到 1.1 秒。當你的反彈率與損失的佣金直接相關時,這很重要。

真實世界架構示例

以下是我們為地中海經紀網站使用過的架構:

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Sanity     │     │  Yacht API   │     │  Redis      │
│  (Marketing  │     │  (Listings,  │     │  (Translation│
│   content)   │     │   specs)     │     │   cache)    │
└──────┬───────┘     └──────┬───────┘     └──────┬──────┘
       │                    │                    │
       └────────────┬───────┘────────────────────┘
                    │
            ┌───────▼───────┐
            │   Next.js     │
            │   App Router  │
            │   + next-intl │
            └───────┬───────┘
                    │
            ┌───────▼───────┐
            │   Vercel      │
            │   Edge Network│
            └───────────────┘

Sanity 處理行銷頁面、團隊簡歷、部落格文章——所有這些都有原生多語言支援。遊艇 API(任一第三方服務或自訂 Supabase 後端)提供房源列表數據。Redis 快取 AI 生成的翻譯。Next.js 使用區域設置感知路由將一切結合在一起。

如果這種架構聽起來像你需要的,我們很樂意討論你的項目。我們為地中海經紀公司構建過其中幾個,並且已經優化了模式。

常見問題

地中海遊艇網站應該支援多少種語言? 至少,你需要英語加上主要市場的當地語言。對於認真的經紀公司,我建議英語、法語、德語和義大利語作為基準——這涵蓋了大約 80% 的地中海遊艇買家。如果你的目標是超過 500 萬歐元的超豪華細分市場,請添加俄語和阿拉伯語。

我應該為遊艇房源使用機器翻譯還是聘請專業翻譯人員? 兩者。對所有房源列表使用 AI 翻譯(GPT-4o 或 Claude)作為第一步,然後讓人類翻譯人員審查你的價格閾值以上的房源。對於 500 字的描述,AI 翻譯成本不到 $0.02,並且讓你達到 90% 的進度。高級房源的人工審查成本為每份描述 $20-50,但確保高價值銷售的準確性。

哪個 CMS 最適合多語言遊艇網站? Sanity 和 Contentful 開箱即用都能很好地處理多語言內容。Sanity 的文檔級本地化提供了更多靈活性,而 Contentful 的欄位級本地化設定更簡單。對於遊艇房源數據本身,我建議使用單獨的專用系統,而不是試圖將所有內容強制放入通用 CMS。查看我們的 headless CMS 開發頁面 以了解更多詳情。

如何在不同的單位系統中處理遊艇測量值? 在數據庫中以公制存儲所有測量值(米、千瓦)。僅基於使用者的區域設置在顯示層轉換為英制。遊艇行業在歐洲普遍使用公制,但美國買家期望英尺和馬力。對於一致的格式化,使用 Intl.NumberFormat API。

hreflang 標籤對遊艇 SEO 真的有幫助嗎? 絕對有。沒有 hreflang 標籤,Google 可能會向德國搜尋者顯示你的法文房源,或更糟的是,將你的翻譯頁面視為重複內容。我們看到在正確實施 hreflang 的經紀網站上,有機流量增加了 40-60%,該網站之前處理它是錯誤的。

構建多語言遊艇經紀網站的成本是多少? 一個設定恰當的多語言遊艇網站,具有 4-6 種語言、CMS 整合和遊艇房源列表管理,通常成本為 $30,000-80,000,具體取決於複雜性。最大成本驅動因素是語言數量、自訂搜尋/篩選功能,以及與現有遊艇管理系統的整合。訪問我們的 定價頁面 以了解更具體的估算。

我可以稍後向遊艇網站添加語言嗎? 是的,如果它從一開始就正確構建。透過適當的 i18n 架構,添加新語言意味著創建新的翻譯檔案、翻譯靜態 UI 字符串,以及通過翻譯管道運行房源列表描述。路由和基礎設施應該已經處理它。如果你當前的網站不是按照 i18n 考量建構的,改造會更困難——但仍然可行。

遊艇網站的阿拉伯語等從右到左的語言呢? 阿拉伯語對地中海遊艇銷售越來越重要,特別是在 1000 萬歐元以上的細分市場。你的 CSS 需要支援 RTL 佈局——使用邏輯屬性(margin-inline-start 而不是 margin-left)並徹底測試。Next.js 支援 RTL,每個區域設置上使用 dir 屬性在 HTML 元素上。它增加了開發時間,但中東買家代表一個重要且不斷增長的市場細分。