Makaleler
Yalnızca Ön Yüzle "Akış + Makale" Yapısında Çok Dilli Bir Blog Yapmak
Kendi sitemi "akış + makale" yapısında çok dilli bir bloga çevirdim: içerik Markdown ile yönetiliyor, çeviri yazı yayımlanırken yapılıyor, statik sayfalar derleme sırasında üretiliyor, tarayıcı ise okuma ve dil değiştirmeyi üstleniyor. Bu yazı içerik modelini, ortak işlem hattını, isteğe bağlı yüklemeyi ve SEO ödünleşimlerini, ayrıca yalnızca ön yüzle neyden vazgeçtiğimi anlatıyor.
· Güncellendi · 7 dk okuma
Sitemde aklıma gelen bir düşünceyi hemen not edebilmek, bir teknik sorunu da baştan sona yazabilmek istiyordum. Kısa olanlar "akış"a, uzun olanlar "makale"ye gidecek; ikisi birbirine bağlanabilecek ve ziyaretçi dili değiştirdiğinde içerik de birlikte değişecekti.
Bu ihtiyaç sıradan görünüyor, ama en başta veritabanı, yönetim paneli ve içerik API'si ekleyip bir de her dil sürümüyle uğraşırsam, kısa sürede uzun vadeli bakım isteyen başka bir ürüne dönüşür.
İhtiyacı gerçekten kullanacağım kapsamla sınırlamayı seçtim: tek yazar, içerik Git'te, yazı yayımlandıktan sonra site yeniden derlenir, okuma tarafında yalnızca statik dosyalar ve tarayıcı yeterli. stephen.taipei'nin şimdiki "akış + makale" mimarisi bu.
Yalnızca ön yüz, okuma tarafı demek#
Önce sınırı netleştireyim. Bu proje React, Vite ve Tailwind kullanıyor; içerik işleme, görsel dönüştürme ve derleme betikleri hâlâ Node.js gerektiriyor; çeviri de yazı yayımlama aşamasında harici bir servis çağırıyor. Dağıtımdan sonra statik dosyaları Nginx sunuyor.
Yani "yalnızca ön yüz" derken kastettiğim şu: okuyucu siteyi açtığında veri çekmek, sayfa oluşturmak ya da anlık çeviri yapmak için özel bir blog arka ucuna ihtiyaç yok. Derleme süreci de sunucu da var, ama makale okumak için sürekli çalışan bir uygulama API'si ya da veritabanı yok.
İşi önceden bitirirseniz okuma tarafı çok daha basit kalıyor.
Akış ve makale: ortak kaynak, ayrı modeller#
İçerik content/ altında toplanıyor ve özgün metinlerin hepsi Markdown. İki içerik türünü zorla aynı biçime sokmadım:
content/
tags.yaml
tags.locales.yaml
notes/2026/20261004-0944.md
notes/2026/20261004-0944.locales.yaml
articles/frontend-only-multilingual-blog/
index.md
locales.yaml
cover.webp
Akış girdisi kısa bir karttır; dosya adı olarak Taipei saatiyle dakika kullanılır ve id görevi görür. Düşünce, öğrenilen şey, yayına alma, tuzağa düşme ya da okuma olarak etiketlenebilir. Tek bir paragraf yazmak yeter, her seferinde başlık düşünmek gerekmez. 500 karakteri aşınca uyarı verilir, 1000 karakteri aşınca makale olarak yazılması gerekir.
Makalenin ise sabit bir slug'ı, başlığı, özeti ve tarihi vardır; kapak görseli ve güncellenme zamanı da olabilir. Adres yayımlandıktan sonra değişmez, böylece paylaşılmış bağlantılar kırılmaz.
İkisi arasında article: ilişkisi kurulur. Akış girdisi önce sorunu not edebilir, sonra tam makaleye bağlanabilir; makale sayfası da kendisine atıf yapan akış girdilerini ters yönde listeler. Kısa içerik anın başlangıç noktasını korur, uzun içerik çıkarımı sonuna kadar yazar; aynı tam metni iki yere yapıştırmaya gerek kalmaz.
İçerik kurallarıyla hataları derlemeden önce yakalamak#
Markdown ekrana olduğu gibi basılırsa birçok sorun ancak yayına çıktıktan sonra fark edilir: yanlış yazılmış tarih, ulaşılamayan makale, alt metni olmayan görsel ya da aynı konunun üç benzer etiketle yazılması.
Bu kuralları ortak içerik işlem hattına koydum. loadContent dosyaları okur, alanları Zod ile denetler, ardından Markdown'ı, görselleri, dilleri ve içerik ilişkilerini işler. CLI denetimi de Vite derlemesi de aynı hattı kullanır; böylece "yerelde çalışıyor ama üretimde çalışmıyor" farkı azalır.
Etiketler yalnızca tags.yaml içinde kayıtlı key'leri kabul eder; akış girdisinin atıf yaptığı makale ve diğer akış girdileri var olmak zorundadır; yerel önizleme taslakları okuyabilir, resmî derleme taslakları hariç tutar. Bu kurallar yazı yazarken hatırlamaya bırakılmıyor, denetim gerçekten engelliyor.
Markdown çıktısının da net sınırları var: ham HTML doğrudan çıktıya verilmez, üretilen HTML temizlenir, kod blokları derleme sırasında renklendirilir. Yerel göreli yoldaki içerik görselleri en fazla 1600px genişliğinde WebP'ye dönüştürülür ve GPS dahil EXIF verisi silinir; makale, kart ve paylaşım önizlemesi işlenmiş aynı görseli kullanır. Uzak görseller ile kök dizin mutlak yolundaki görseller bu yerel dönüştürme akışına girmez.
Çok dilliliğin maliyetini yazı yayımlarken ödemek#
Site şu anda 22 dili destekliyor. Geleneksel Çince özgün metni bir kez yazıyorum; diğer 21 dilin başlığı, özeti, gövde metni ve görsel açıklamaları yanındaki locales.yaml dosyasında saklanıyor.
Burada önemsediğim bir karar var: okuyucu çeviri için beklemez. content:localize çeviriyi yazı yayımlanırken üretir; resmî build yalnızca halihazırda var olan dosyaları okur, her dağıtımda çeviri servisini yeniden çağırmaz.
Özgün metinle çeviri arasında source digest ile eşleştirme yapılır. Özgün metin değiştiyse eski çeviri artık güncel sürüm sayılamaz; bir dil eksikse ya da çeviri eskimişse content:check başarısız olur. Çeviri sürecinde kod, inline code ve bağlantı hedefleri korunur; teknik tanımlayıcılar ya da URL'ler yanlışlıkla çevrilmez.
Bu, otomatik çevirinin insan editörlüğü kalitesinde olduğu anlamına gelmez. Çözdüğü şey bütünlük ve güncelleme tutarlılığıdır; teknik terimler ve ton için yine de gözden geçirme gerekir. Değişmeyen özgün metnin mevcut çevirisi korunur, elle düzeltilmiş metin de yeniden çalıştırmayla ezilmez.
Listede özet okunur, makale tıklanınca yüklenir#
İçerik arttıktan sonra ana sayfa her makalenin ve her dilin tam metnini baştan yüklerse, yalnızca ön yüzlü bir site bile yavaşlar.
Vite eklentisi içeriği üç girişe ayırır: ana sayfa için virtual:content/latest, içerik sayfaları için virtual:content/index ve her makaleye özel virtual:content/article/<slug>. Ana sayfa en yeni üç akış girdisini ve üç makale kartını alır; akış verisi tam metni ve çevirileri içerse de kartta kesilerek gösterilir. Uzun makale kartı yalnızca özet gibi verileri taşır, tam gövde ise dynamic import ile makale makale yüklenir.
React'in Suspense'i gövdeyi beklemeyi, dıştaki hata sınırı da yükleme hatası mesajını göstermeyi üstlenir. Gövde isteği başarısız olursa başlık, özet ve sayfa gezintisi yerinde kalır.
Burada da bir ödünleşim var: şu an bir makale için tek bir gövde chunk'ı var ve içinde tüm dillerin çevirileri bulunuyor; henüz dil başına bir chunk'a bölünmedi. Akış girdilerinin tam metni ve çevirileri ise içerik dizinine giriyor. İçerik büyümeye devam ederse dizin boyutu ile çok dilli gövdelerin indirme miktarı, ölçülmesi gereken bir sonraki nokta.
Ön yüzde geçiş olsun, ama statik okuma girişi de olsun#
Yalnızca SPA olsaydı, paylaşıldığında çoğu zaman yalnızca ana sayfanın başlığı ve görseli görünürdü. Makale için bu yetmez.
Bu yüzden derleme sırasında her akış girdisi, makale ve etiket adresi kendi HTML shell'ine sahip olur; içine title, description, canonical, Open Graph ve JSON-LD yazılır, noscript içine de o dilde okunabilir metin konur. JavaScript çalışmasa bile makalenin içeriğine ve özel kapağına ulaşılabilir.
Aynı içerikten ayrıca feed.xml, sitemap-content.xml, makalenin Markdown sürümü ve llms.txt makale dizini üretilir. Bunların hepsi derleme çıktısıdır; elle eşitlenmesi gereken ayrı bir içerik seti değildir.
Çok dilli SEO yaklaşımını da açıkça söyleyeyim: akış girdileri, makaleler ve etiketlerin her dilde kendi adresi var. zh-TW için /articles/<slug>, diğer diller için önek eklenir; örneğin /en/articles/<slug>. Derleme sırasında her dil kendi statik sayfasını üretir, self-canonical ve hreflang taşır, hepsi sitemap-content.xml içinde listelenir; feed ve Markdown sürümü özgün dilde kalır.
Ana sayfa ise tek adreste kalır ve tarayıcı diline ile açık/koyu moda göre önceden oluşturulmuş sürümü sunar. Her içeriğin 22 dili, arama motorlarınca ayrı ayrı dizinlenebilen bağımsız sayfalardır.
Yazı yayımlamak da bir teslimat hattı#
Yazı yayımlama akışım şöyle: özgün metni yaz, çeviriyi üret, içeriği denetle, Git'e commit et, sonra main'e birleştir. Mevcut GitHub Actions siteyi yeniden derleyip dağıtır.
npm run content:localize
npm run content:check
npm run test:content
npm run build
Diller tam değilse önce onu düzeltirim. Makale taslaksa yerel önizlemede kalır. Resmî çıktıda ayrıca taslak sayfalarının bulunmadığı denetlenir; feed, sitemap, ana sayfa ve JavaScript bundle gibi başlıca herkese açık çıktılarda taslak işaretleri taranır. İçeriğin de kod gibi diff'i ve sürümü vardır, birlikte geri alınabilir.
Bu yöntem anlık güncellemeden ödün veriyor: tek bir cümleyi değiştirmek bile bir derleme ve dağıtım beklemeyi gerektirir. Ama kendi teknik sitem için bu beklemeyi göze alırım; karşılığında bir yönetim paneli, bir çalışma zamanı bağımlılığı eksik olur ve değişiklik kayıtlarını izlemek kolaylaşır.
Bu yaklaşım ne zaman uygun#
Tek yazar varsa, yazı sıklığı yeniden derlemeyi kaldırabiliyorsa, içerik ağırlıklı olarak metinse ve Git'e alışkınsanız bu mimari çok uygundur. Kısa akış girdileri yazma eşiğini düşürür, makaleler tam çıkarımı korur, çok dillilik aynı içeriği daha çok kişiye ulaştırır.
İhtiyaç çok kullanıcılı anlık iş birliğine, yetki yönetimine, okuyucu yorumlarına, girişe ya da aboneliğe dönüşürse CMS ve arka uç yeniden değerlendirilmelidir. Yalnızca ön yüz bu ihtiyaçları kendiliğinden ortadan kaldırmaz; ben sadece şimdilik onları bu blogun kapsamına almadım.
Bu seferki tasarım ilkesi, çok yerleşimli ve çok temalı mimari tasarım kalıpları yazımda anlattığıma çok yakın: önce sorumlulukları ayır, sonra ortak kuralların tutarlılığı korumasını sağla.
Bu site için önemli olan, yazmaya devam edebilmem ve her yayında tam olarak hangi içeriğin gönderildiğini bilebilmem.