09 / 09Proje
Çok Dilli Sayfa ve CI Hattı
Mevcut bir sayfayı iki dile çevirip lint, tip ve test kontrollerini CI'a bağlama.
Senaryo
Elinizde tek dilli bir ürün sayfası var. Ad, fiyat, stok ve güncelleme tarihi Türkçe olarak koda gömülü. Göreviniz:
Sayfayı Türkçe ve İngilizce sunmak:
/trve/en./adresini tarayıcı diline göre yönlendirmek.Her pull request'te lint, biçim, tip ve testleri GitHub Actions'ta çalıştırmak.
| Parça | Dosya | Ders |
|---|---|---|
| Yönlendirme, mesajlar, biçimlendirme | src/i18n/, messages/, src/proxy.ts | Çoklu Dil (i18n) |
| ESLint, Prettier, CI | eslint.config.mjs, .github/workflows/ci.yml | Kod Kalitesi ve Ekip Standartları |
| Uçtan uca test | e2e/i18n.spec.ts | Playwright |
Başlangıç ve çözüm GitHub'da: examples/architecture/multilingual-ci-project.
İpucu
Proje next@16.4.2, next-intl@4.14.9, pnpm@10.28.0, vitest@5.0.5 ve @playwright/test@1.64.1 ile yazıldı. cacheComponents açık bırakıldı.
Adım 1: Metinleri Çıkarın
Önce sayfadaki her metni bulun. starter/src/app/page.tsx içinde beş tane var: ürün adı, fiyat, stok, tarih ve buton. layout.tsx'te de başlık ve açıklama duruyor.
Ürün adı veriden gelir, arayüz metni değildir. Onu messages/ dosyasına değil veriye koyun:
// src/data/product.ts
export const product = {
name: { tr: "Mekanik Klavye", en: "Mechanical Keyboard" },
price: 2499.9,
stock: 3,
updatedAt: new Date("2026-10-15T21:00:00Z"),
};Adım 2: next-intl'i Kurun
$ pnpm add next-intlKurulum Çoklu Dil (i18n) dersindekiyle aynı: routing.ts, navigation.ts, request.ts, proxy.ts ve app/[locale]/ klasörü.1 Tek ek, request.ts'te saat dilimi:
// src/i18n/request.ts
return {
locale,
timeZone: "Europe/Istanbul",
messages: (await import(`../../messages/${locale}.json`)).default,
};Saat dilimini vermezseniz tarih sunucunun saat dilimine göre yazılır.2 Ürün UTC'de 15 Ekim 21:00'de güncellendi; İstanbul'da bu 16 Ekim.
Adım 3: Mesajları Yazın
// messages/tr.json
"ProductPage": {
"price": "Fiyat: {price, number, ::currency/TRY}",
"stock": "{count, plural, =0 {Stokta yok} other {Son # ürün}}",
"updated": "Güncelleme tarihi: {date, date, long}",
"addToCart": "Sepete ekle"
}// messages/en.json
"ProductPage": {
"price": "Price: {price, number, ::currency/TRY}",
"stock": "{count, plural, =0 {Out of stock} one {Last one left} other {Only # left}}",
"updated": "Updated on {date, date, long}",
"addToCart": "Add to cart"
}Çoğul: Türkçe sayıyla birlikte ismi tekil kullanır,
onedalı gerekmez. İngilizcedeoneayrı yazılır.3Para birimi: İki dilde de
TRY. Para birimine ürün karar verir, dil yalnızca gösterimi değiştirir. Denemede Türkçe₺2.499,90, İngilizceTRY 2,499.90çıktı;endilinde lira için bir sembol yok, kod yazılıyor.4
Adım 4: Sayfa ve Dil Değiştirici
// src/app/[locale]/page.tsx
const t = useTranslations("ProductPage");
const locale = useLocale() as Locale;
<h1>{product.name[locale]}</h1>
<p data-testid="price">{t("price", { price: product.price })}</p>
<p data-testid="stock">{t("stock", { count: product.stock })}</p>Dil değiştirici @/i18n/navigation'daki Link'i kullanır: <Link href="/" locale="en">EN</Link>.1 Başlık ve açıklama generateMetadata içinde getTranslations("Metadata") ile çevrilir.
$ pnpm build && pnpm start
curl -I -H "Accept-Language: en-US" http://localhost:3000/Yanıtta location: /en gelmeli. /tr yanıtındaki link başlığında hreflang="tr", hreflang="en" ve x-default görünür.
Adım 5: Kontrolleri Yazın
CI'ın çalıştıracağı her kontrol önce yerelde bir script olmalı:
// package.json
"scripts": {
"lint": "eslint",
"format:check": "prettier --check .",
"check-types": "next typegen && tsc --noEmit",
"test": "vitest run",
"test:e2e": "playwright test"
}check-types önce next typegen çalıştırır. Temiz bir kopyada PageProps ve LayoutProps tipleri henüz üretilmediği için tsc tek başına hata verir.
Eksik çeviri testi. En sık hata, bir dile eklenip diğerine eklenmeyen anahtardır. Küçük bir Vitest testi iki dosyanın anahtarlarını karşılaştırır:
// src/i18n/messages.test.ts
test("every locale has the same message keys", () => {
expect(keys(en).sort()).toEqual(keys(tr).sort());
});Denemede en.json'dan addToCart silinince test kırmızıya döndü.
Uçtan uca test. Playwright /tr'yi açar, değerleri kontrol eder ve EN linkine tıklar.
Uyarı
cacheComponents açıkken Next.js önceki sayfayı silmez, <Activity> ile display: none yapıp DOM'da tutar.5 Dil değiştirdikten sonra getByTestId("price") iki eleman buldu: görünen İngilizce ve gizli Türkçe. getByRole gizli elemanları atladığı için kontrolleri görünen <main> içine alın:
const main = page.getByRole("main");
await expect(main.getByTestId("price")).toHaveText("Price: TRY 2,499.90");Adım 6: CI'a Bağlayın
# .github/workflows/ci.yml
jobs:
quality:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: pnpm/setup@c9883cc79df532ad1a7b81bf9ab944ceb090d65c # v2.0.0
with:
runtime: node@24
cache: true
- run: pnpm lint
- run: pnpm format:check
- run: pnpm check-types
- run: pnpm test
e2e:
runs-on: ubuntu-24.04
steps:
# checkout ve pnpm/setup aynı
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm test:e2e
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/pnpm/setuppnpm'i, Node.js'i kurar vepnpm install'ı kendisi çalıştırır.6İki iş paralel çalışır. Hızlı kontroller, uzun süren tarayıcı testini beklemez.
Test kırılınca rapor artifact olarak yüklenir; Playwright dokümanındaki örnek de böyle.7
playwright.config.ts'teforbidOnly: !!process.env.CIvar: unutulmuş birtest.onlyCI'ı kırar.9
Son olarak GitHub'da quality ve e2e işlerini required status check yapın. Böylece kontroller geçmeden birleştirme yapılamaz.8
İpucu
Bu workflow'un her komutu yerelde ve temiz kurulumda denendi. GitHub Actions'ta çalıştırılmadı; ilk PR'ınızda çıktısına bakın.
Kendin Dene
Almanca ekleyin:
messages/de.json'u eksik bir anahtarla oluşturun ve testin yakaladığını görün.localePrefix: "as-needed"yapın. Playwright testlerinde hangi URL'leri değiştirmeniz gerekiyor?Bir PR'da bilerek biçimi bozun ve
format:checkadımının kırmızıya döndüğünü görün.
Kaynaklar
`routing.ts`, `proxy.ts`, `navigation.ts`, `request.ts` ve `[locale]` segmenti.
`getRequestConfig` içinde `timeZone`, `messages` ve `formats`.
ICU argüman, `plural` kategorileri ve tam eşleşme.
`currency` ve `currencyDisplay` seçenekleri.
`cacheComponents` ile önceki sayfaların `<Activity>` ile gizlenip DOM'da tutulması.
`pnpm/setup` ile GitHub Actions, önbellek ve `packageManager` alanı.
GitHub Actions workflow'u, tarayıcı kurulumu ve raporu artifact olarak yükleme.
Zorunlu durum kontrolleri ve benzersiz iş adı uyarısı.
`forbidOnly` ve CI'a göre ayarlanan seçenekler.
Son güncelleme tarihi: 22 Ekim 2026
