Rehberler//10 dk okuma

SvelteKit i18n: Svelte 5 ile Tip Güvenli Çok Dilli Uygulamalar Geliştirme

Eray Gündoğmuş
Paylaş

SvelteKit i18n: Svelte 5 ile Tip Güvenli Çok Dilli Uygulamalar Geliştirme

SvelteKit'in mimarisi, uluslararasılaştırmaya oldukça uygun bir yapı sunar. Sunucu taraflı yükleme fonksiyonları render öncesinde çalıştığından, yerel ayar tespiti ve mesaj yükleme işlemleri çerçeveyle uyumlu biçimde gerçekleşir. Düzen hiyerarşilerine sahip URL tabanlı yönlendirme, framework'le çatışmadan rotaları /en/ veya /de/ önekleriyle kullanmanızı sağlar. Svelte 5'in runes'ları — $state, $derived, $effect — ise Svelte 4'te gereken standart store'ların yerini alarak reaktif yerel ayar değiştirmeyi mümkün kılar.

Bu kılavuz, Svelte 5 ile üretime hazır çok dilli bir SvelteKit uygulaması geliştirme sürecini adım adım ele alır. Yerel ayar tespiti, tip güvenli çeviri fonksiyonları, Intl API ile çoğullama ve SEO konularını inceleyeceğiz. Ayrıca çeviri dosyalarını paketleme ile CDN üzerinden sunma arasındaki dengelere dürüstçe bakacağız.

Proje Kurulumu

Yeni bir SvelteKit projesiyle başlayın (TypeScript şablonu):

npx sv create my-i18n-app
cd my-i18n-app
npm install

Bu kılavuzda bağımlılıkları minimumda tutacağız — modern tarayıcılara yerleşik Intl API'leri biçimlendirmeyi üstlenir ve ince bir çeviri katmanını kendimiz yazacağız. ICU mesaj formatı desteğine ihtiyaç duyarsanız @messageformat/core gibi kütüphaneler kolayca entegre edilebilir.

Hedeflediğimiz klasör yapısı:

src/
  lib/
    i18n/
      index.ts          # Temel çeviri yardımcıları
      types.ts          # Üretilen/paylaşılan tipler
      locales/
        en.json
        de.json
        fr.json
  hooks.server.ts       # Yerel ayar tespiti
  routes/
    [locale]/
      +layout.server.ts # Çevirileri yükle
      +layout.svelte    # Çevirileri ağaca aktar
      +page.svelte

Desteklenen yerel ayarları paylaşılan bir konfigürasyonda tanımlayın:

// src/lib/i18n/config.ts
export const SUPPORTED_LOCALES = ['en', 'de', 'fr'] as const;
export type Locale = typeof SUPPORTED_LOCALES[number];
export const DEFAULT_LOCALE: Locale = 'en';

export function isSupportedLocale(locale: string): locale is Locale {
  return SUPPORTED_LOCALES.includes(locale as Locale);
}

Yerel Ayar Tespiti ve Yönlendirme

Herhangi bir rota çalışmadan önce kullanıcının yerel ayarını tespit etmek için SvelteKit'in hooks.server.ts dosyası doğru yerdir. Önce URL'yi okuyacak, ardından Accept-Language başlığına, son olarak da varsayılan değere geri döneceğiz.

// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { DEFAULT_LOCALE, isSupportedLocale } from '$lib/i18n/config';

export const handle: Handle = async ({ event, resolve }) => {
  // URL yol bölümünden yerel ayarı çıkar
  const pathLocale = event.url.pathname.split('/')[1];

  const locale = isSupportedLocale(pathLocale)
    ? pathLocale
    : detectFromHeader(event.request.headers.get('accept-language'));

  event.locals.locale = locale;

  return resolve(event, {
    transformPageChunk: ({ html }) => html.replace('%lang%', locale),
  });
};

function detectFromHeader(header: string | null): import('$lib/i18n/config').Locale {
  if (!header) return DEFAULT_LOCALE;

  const accepted = header
    .split(',')
    .map((part) => {
      const [lang, q = 'q=1'] = part.trim().split(';');
      return { lang: lang.split('-')[0].trim(), q: parseFloat(q.split('=')[1]) };
    })
    .sort((a, b) => b.q - a.q);

  for (const { lang } of accepted) {
    if (isSupportedLocale(lang)) return lang;
  }

  return DEFAULT_LOCALE;
}

Uygulamanızın locals tipine locale alanını ekleyin:

// src/app.d.ts
import type { Locale } from '$lib/i18n/config';

declare global {
  namespace App {
    interface Locals {
      locale: Locale;
    }
  }
}

URL tabanlı yönlendirme için üst düzeyde bir [locale] segmenti oluşturun ve doğrulamak amacıyla bir params eşleştiricisi ekleyin:

// src/params/locale.ts
import { isSupportedLocale } from '$lib/i18n/config';

export function match(param: string): boolean {
  return isSupportedLocale(param);
}

Bu eşleştiriciye rota dizininizde şu şekilde başvurun: src/routes/[locale=locale]/. SvelteKit yalnızca segmentin geçerli bir yerel ayar dizesi olduğu yolları eşleştirecektir.

Çevirileri Yükleme

[locale] segmentinin altındaki +layout.server.ts dosyasında, mevcut yerel ayar için çevirileri yükleyin. Bu işlem sayfa render edilmeden önce sunucu tarafında çalışır; dolayısıyla çevrilmemiş içeriğin anlık görünümü yaşanmaz.

// src/routes/[locale=locale]/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ params, fetch }) => {
  const locale = params.locale;

  // Seçenek A: Paketlenmiş JSON (doğrudan içe aktarma)
  const messages = await import(`$lib/i18n/locales/${locale}.json`);

  // Seçenek B: CDN endpoint'i (çeviri güncellemeleri için yeniden dağıtım gerekmez)
  // const res = await fetch(`https://cdn.example.com/translations/${locale}.json`);
  // const messages = await res.json();

  return { locale, messages: messages.default };
};

Düzen bileşeninde çevirileri alt bileşenlere erişilebilir kılın:

<!-- src/routes/[locale=locale]/+layout.svelte -->
<script lang="ts">
  import { setContext } from 'svelte';
  import { createTranslator } from '$lib/i18n';
  import type { LayoutData } from './$types';

  const { data }: { data: LayoutData } = $props();

  const t = createTranslator(data.messages);
  setContext('t', t);
  setContext('locale', data.locale);
</script>

<slot />

Svelte 5 Runes ile Bileşenlerde Çeviri Kullanımı

Svelte 5 ile yerel ayar değiştiğinde çevirileri reaktif hale getirmek için $state ve $derived kullanabilirsiniz. Aşağıda runes ile temiz çalışan bir çeviri store deseni verilmiştir:

// src/lib/i18n/index.ts
import type { Messages } from './types';

export function createTranslator(messages: Messages) {
  return function t(key: keyof Messages, params?: Record<string, string | number>): string {
    const template = messages[key] ?? key;

    if (!params) return template;

    return Object.entries(params).reduce<string>(
      (result, [k, v]) => result.replaceAll(`{${k}}`, String(v)),
      template
    );
  };
}

Bir bileşende çevirmeni context'ten alın:

<!-- src/routes/[locale=locale]/+page.svelte -->
<script lang="ts">
  import { getContext } from 'svelte';
  import type { Translator } from '$lib/i18n/types';

  const t = getContext<Translator>('t');
  const locale = getContext<string>('locale');

  // Svelte 5: context'ten türetilmiş durum
  let greeting = $derived(t('home.greeting', { name: 'World' }));
</script>

<h1>{greeting}</h1>
<p>{t('home.description')}</p>

Tam sayfa yenilemesi olmadan yerel ayar değiştirmek için $state kullanarak çevirmeni reaktif biçimde güncelleyin:

<script lang="ts">
  import { createTranslator } from '$lib/i18n';
  import type { Messages } from '$lib/i18n/types';

  let messages = $state<Messages>({} as Messages);
  let t = $derived(createTranslator(messages));

  async function switchLocale(newLocale: string) {
    const res = await fetch(`/api/translations/${newLocale}`);
    messages = await res.json();
    // Yeniden yükleme olmadan URL'yi güncelle
    history.pushState({}, '', `/${newLocale}${window.location.pathname.replace(/^\/[^/]+/, '')}`);
  }
</script>

Tip Güvenliği

Tip güvenli bir çeviri fonksiyonu, eksik anahtarları derleme zamanında yakalar ve IDE otomatik tamamlamasını etkinleştirir. Temel yerel ayar dosyanızdan tipler üretin:

// src/lib/i18n/types.ts
// en.json'dan otomatik üretilir — elle düzenlemeyin
import type en from './locales/en.json';

export type Messages = typeof en;
export type MessageKey = keyof Messages;
export type Translator = (key: MessageKey, params?: Record<string, string | number>) => string;

Bu adımı otomatikleştirebilirsiniz. Tipleri yeniden oluşturmak için basit bir betik:

// scripts/generate-i18n-types.ts
import { writeFileSync } from 'fs';

const output = `// Auto-generated from en.json — do not edit manually
import type en from './locales/en.json';

export type Messages = typeof en;
export type MessageKey = keyof Messages;
export type Translator = (key: MessageKey, params?: Record<string, string | number>) => string;
`;

writeFileSync('src/lib/i18n/types.ts', output);
console.log('Types generated.');

Bunu package.json derleme sürecinize bağlayın:

{
  "scripts": {
    "generate:i18n": "tsx scripts/generate-i18n-types.ts",
    "prebuild": "npm run generate:i18n",
    "predev": "npm run generate:i18n"
  }
}

Artık t('home.greetng') bir TypeScript hatası üretir, çünkü 'home.greetng' Messages içinde yer almaz. Yazım hataları üretimde kullanıcılar tarafından keşfedilmek yerine derleme zamanında yakalanır.

Bu, Better i18n gibi platformların gerçek değer kattığı bir alandır: çeviri şemanızdan SDK tiplerini otomatik olarak üretirler; böylece bir çevirmen anahtar eklediğinde veya yeniden adlandırdığında TypeScript tipleriniz manuel betik çalıştırmaya gerek kalmadan güncellenir.

Svelte ile birlikte React veya Next.js uygulamaları geliştiriyorsanız aynı tip güvenli desenlerin geçerli olduğunu göreceksiniz — framework'e özgü uygulamalar için React i18n: React Uluslararasılaştırmasına Kapsamlı Kılavuz ve Next.js App Router i18n: Server Components ve RSC Desenleri makalelerine bakabilirsiniz.

Çoğullama ve Biçimlendirme

JavaScript'in yerleşik Intl API'si, harici bağımlılıklar olmadan yerel ayarlar arasında doğru çoğullamayı gerçekleştirir:

// src/lib/i18n/format.ts

export function pluralize(
  locale: string,
  count: number,
  forms: Record<Intl.LDMLPluralRule, string>
): string {
  const rule = new Intl.PluralRules(locale).select(count);
  const template = forms[rule] ?? forms.other;
  return template.replace('{count}', String(count));
}

export function formatNumber(locale: string, value: number, options?: Intl.NumberFormatOptions): string {
  return new Intl.NumberFormat(locale, options).format(value);
}

export function formatDate(locale: string, date: Date, options?: Intl.DateTimeFormatOptions): string {
  return new Intl.DateTimeFormat(locale, options).format(date);
}

Bir bileşende kullanım:

<script lang="ts">
  import { pluralize, formatNumber, formatDate } from '$lib/i18n/format';
  import { getContext } from 'svelte';

  const locale = getContext<string>('locale');

  const itemCount = 3;
  const price = 1499.99;
  const publishedAt = new Date('2024-06-15');

  let itemLabel = $derived(
    pluralize(locale, itemCount, {
      one: '{count} item',
      other: '{count} items',
      zero: 'no items',
      two: '{count} items',
      few: '{count} items',
      many: '{count} items',
    })
  );

  let formattedPrice = $derived(formatNumber(locale, price, { style: 'currency', currency: 'USD' }));
  let formattedDate = $derived(formatDate(locale, publishedAt, { dateStyle: 'long' }));
</script>

<p>{itemLabel}</p>
<p>{formattedPrice}</p>
<p>{formattedDate}</p>

Alman yerel ayarında 1499.99 değeri 1.499,99 $ olarak, tarih ise 15. Juni 2024 biçiminde oluşturulur — ekstra kütüphane gerekmez.

Çok Dilli SvelteKit için SEO

Arama motorları, sayfalarınızın alternatif dil sürümleri hakkında açık sinyallere ihtiyaç duyar. hreflang etiketlerini ve yerelleştirilmiş meta verileri düzeninizin <svelte:head> bölümüne ekleyin. Bunu doğru yapmak çok dilli sıralamalar için kritik öneme sahiptir — i18n SEO, hreflang etiketleri ve yerel ayar URL'lerine kapsamlı kılavuzumuz her uygulama ayrıntısını ve yaygın tuzakları kapsar.

<!-- src/routes/[locale=locale]/+layout.svelte -->
<script lang="ts">
  import { page } from '$app/stores';
  import { SUPPORTED_LOCALES } from '$lib/i18n/config';

  const { data }: { data: LayoutData } = $props();

  // Desteklenen tüm yerel ayarlar için alternatif URL'ler oluştur
  let alternates = $derived(
    SUPPORTED_LOCALES.map((loc) => ({
      locale: loc,
      url: $page.url.href.replace(`/${data.locale}/`, `/${loc}/`),
    }))
  );
</script>

<svelte:head>
  {#each alternates as { locale, url }}
    <link rel="alternate" hreflang={locale} href={url} />
  {/each}
  <link rel="alternate" hreflang="x-default" href={alternates.find(a => a.locale === 'en')?.url} />
</svelte:head>

<slot />

Ön oluşturma için SvelteKit'i tüm yerel ayar varyantlarını üretecek şekilde yapılandırın:

// svelte.config.js
const config = {
  kit: {
    prerender: {
      entries: [
        '/en',
        '/de',
        '/fr',
        '/en/about',
        '/de/about',
        '/fr/about',
        // ... veya dinamik olarak oluşturun
      ],
    },
  },
};

Dinamik ön oluşturma girdileri için entries dışa aktarması olan bir +page.server.ts kullanın:

// src/routes/[locale=locale]/+page.server.ts
import { SUPPORTED_LOCALES } from '$lib/i18n/config';

export function entries() {
  return SUPPORTED_LOCALES.map((locale) => ({ locale }));
}

CDN Öncelikli ve Paketlenmiş Çeviriler

Her iki yaklaşım da çalışır — doğru seçim iş akışınıza bağlıdır.

Paketlenmiş çeviriler (JSON'dan içe aktarma):

  • Sayfa yükleme başına ek HTTP isteği yok
  • Çeviriler dağıtımla versiyon kilitleme
  • Çevrimdışı ve harici bağımlılıklar olmadan çalışır
  • Bir çevirideki yazım hatasını düzeltmek yeniden dağıtım gerektirir

CDN ile dağıtım:

  • Çeviri güncellemeleri uygulamayı yeniden dağıtmadan yayına girer
  • Çevirmenler değişiklikleri mühendislerden bağımsız olarak yayınlayabilir
  • Ağ isteği ekler (agresif önbellekleme ile azaltılabilir)
  • Önbellek geçersiz kılma stratejisi gerektirir

i18n yolculuğunun başında olan çoğu ekip için paketlenmiş JSON daha basittir. Çeviri ekibiniz büyüdükçe ve içerik güncelleme hızı arttıkça dağıtım bağımlılığı zahmetli hale gelir. Bir Almanca düğme etiketini düzeltmek için kod sürümü yayınlamak ölçeklenemez. Paketlenmiş yaklaşımın sınırlarına ulaştığınızda CI/CD sürecinizde i18n'i otomatikleştirmek doğal bir sonraki adımdır.

Bu, Better i18n gibi platformların çözdüğü temel sorundur: çeviri mesajlarınız CDN'lerinde, uzun önbellek TTL'leriyle versiyonlu URL'ler üzerinden sunulur. Mühendisler kodu günceller, çevirmenler çevirileri günceller — birbirini engellemeden, bağımsız olarak. Özellikler sayfası bunun nasıl çalıştığını, çeviri yayınında önbellek geçersiz kılmanın nasıl ele alındığı dahil ayrıntılı biçimde ele almaktadır.

Orta bir yol da var: derleme zamanında bir CDN'den çevirileri alın ve sonucu dağıtım artifaktına paketleyin. Yerel ayar dosyalarını reponuza almadan hızlı yerel yüklemeler elde edersiniz ve güncellemeler için hâlâ yeniden dağıtım gerekir.

Özet

SvelteKit ve Svelte 5, çok dilli uygulamalar için sağlam bir temel oluşturur:

  • Hooks, render gerçekleşmeden önce yerel ayar tespitini sunucu tarafında gerçekleştirir
  • Params eşleştiricileri olan dinamik rota segmentleri, temiz URL tabanlı yerel ayar yönlendirmesi sağlar
  • Yükleme fonksiyonları, çevirileri düzen başına bir kez alır ve bileşen ağacına aktarır
  • Svelte 5 runes'ları ($state, $derived), manuel store abonelikleri olmadan yerel ayar değiştirmeyi reaktif kılar
  • Temel yerel ayarınızdan üretilen TypeScript tipleri, eksik veya yanlış yazılmış anahtarları derleme zamanında yakalar
  • Intl API'leri, her yerel ayar için çoğullama ile sayı/tarih biçimlendirmeyi doğru şekilde yönetir
  • svelte:head hreflang etiketleri ve ön oluşturulmuş yerel ayar rotaları arama motorlarını bilgilendirir

Burada açıklanan mimari, üretim gereksinimlerinin büyük bölümünü karşılar. Bıraktığı temel boşluk operasyoneldir: uygulamanız büyüdükçe çeviri dosyalarını repo içinde yönetmek mühendisler ve çevirmenler arasında sürtüşme yaratır. CDN ile dağıtım ve otomatik tip üretimi, amaca özel araçların en hızlı karşılığını verdiği iki alandır.

Gerçek bir çeviri ekibiyle SvelteKit uygulaması geliştiriyorsanız Better i18n'in Svelte entegrasyonuna göz atın — tip güvenli SDK, kullanıma hazır CDN dağıtımı ve çeviri değişikliklerinin kod ile aynı inceleme sürecinden geçmesini sağlayan Git tabanlı bir inceleme iş akışı sunar.

Better i18n, modern frontend ekipleri için tasarlanmış geliştirici odaklı bir yerelleştirme platformudur. Tip güvenli SDK'lar, Git tabanlı iş akışları, CDN dağıtımı ve sözlük uygulaması ile yapay zeka çevirisi — repo'nuzda yerel ayar dosyaları olmadan.


Uygulamanızı better-i18n ile küresele taşıyın

better-i18n; yapay zeka destekli çevirileri, git-native iş akışlarını ve küresel CDN dağıtımını tek bir geliştirici odaklı platformda bir araya getirir. Elektronik tablolarla uğraşmayı bırakın ve her dilde yayınlamaya başlayın.

Ücretsiz başlayın → · Özellikleri keşfet · Belgeleri oku

Comments

Loading comments...