Thymeleaf ile bir statik JBake sitesinin uluslararasılaştırılması
Yayınlandı: 20 October 2025
Giriş
İnternasyonelleştirme (i18n) bir statik sitesi ilk bakışta karmaşık görünebilir, ancak JBake Thymeleaf ile birleştirildiğinde çoklu dil desteği olan bir site oluşturmak için şık çözümler sunar. Bu makalede, blogumda i18n’yi nasıl kurduğumu göstermek, hem şablonlama hem de birden fazla dilde makale yönetimini kapsayacak şekilde ele alacağım.
Kullanım durumu diyagramı (Use Case)
@startuml
left to right direction
skinparam packageStyle rectangle
actor "Yazar" as author
actor "FR Okuyucu" as reader_fr
actor "EN Okuyucu" as reader_en
rectangle "Çok Dilli JBake Sitesi" {
usecase "Fransızca makale yaz" as UC1
usecase "Makale EN yaz" as UC2
usecase "i18n mesajlarını yapılandır" as UC3
usecase "Site oluştur" as UC4
usecase "FR makalesini oku" as UC5
usecase "Makaleyi oku EN" as UC6
usecase "Dili değiştir" as UC7
usecase "Mevcut çevirileri gör" as UC8
}
author --> UC1
author --> UC2
author --> UC3
author --> UC4
reader_fr --> UC5
reader_fr --> UC7
reader_fr --> UC8
reader_en --> UC6
reader_en --> UC7
reader_en --> UC8
UC1 ..> UC4 : <<include>>
UC2 ..> UC4 : <<include>>
UC3 ..> UC4 : <<include>>
UC5 ..> UC8 : <<extend>>
UC6 ..> UC8 : <<extend>>
@enduml
Uluslararasılaştırma mimarisi
Yaklaşımız iki temel üzerine kuruludur :
-
Templating’in i18n’i: Thymeleaf mesaj dosyalarının kullanımı arayüz öğeleri için
-
İçeriğin i18n’i: dil bazlı makalelerin ayrılmış bir klasör yapısında düzenlenmesi
Yapı şeması (Dosya organizasyonu)
@startuml
@startsalt
{
{T
+ projet-jbake
++ content
+++ blog
++++ 2024
+++++ fr
++++++ article1.adoc
++++++ article2.adoc
+++++ en
++++++ article1.adoc
++++++ article2.adoc
++++ 2025
+++++ fr
++++++ guide-i18n.adoc
+++++ en
++++++ i18n-guide.adoc
++ templates
+++ post.html
+++ index.html
+++ index_en.html
+++ messages.properties
+++ messages_fr.properties
+++ messages_en.properties
++ assets
+++ css
+++ js
+++ img
++ jbake.properties
++ output
+++ fr
++++ blog
+++++ 2025
++++++ guide-i18n.html
+++ en
++++ blog
+++++ 2025
++++++ i18n-guide.html
}
}
@endsalt
@enduml
Neden bu yaklaşım?
Bu ayırım izin verirs:
-
Arayüzde dille ne olursa olsun tutarlılığı koruyun
-
içeriği ve makale çevirilerini bağımsız olarak yönet
-
Yeni dillerin eklenmesini büyük bir refaktörleme yapmadan kolaylaştır
-
Belirli dillerde sadece mevcut olan makalelere izin ver
Bileşen diyagramı
@startuml
skinparam componentStyle rectangle
package "Kaynaklar" {
folder "content/blog/2025/" {
folder "fr/" as content_fr {
[article1.adoc]
[article2.adoc]
}
folder "içinde/" as content_en {
[article1.adoc] as article1_en
[article2.adoc] as article2_en
}
}
folder "şablonlar/" {
[post.html]
[index.html]
[index_en.html]
}
folder "mesajlar/" {
[messages_fr.properties]
[messages_en.properties]
}
[jbake.properties]
}
package "JBake Motoru" {
[Parser AsciiDoc] as parser
[Template Engine\nThymeleaf] as thymeleaf
[Generator] as generator
[I18n Resolver] as i18n
}
package "Oluşturulan Site" {
folder "fr/" {
folder "blog/2025/" as blog_fr {
[article1.html]
[article2.html]
}
}
folder "içinde/" {
folder "blog/2025/" as blog_en {
[article1.html] as article1_html_en
[article2.html] as article2_html_en
}
}
}
content_fr --> parser
content_en --> parser
parser --> generator
templates --> thymeleaf
messages --> i18n
i18n --> thymeleaf
thymeleaf --> generator
jbake.properties --> generator
generator --> blog_fr
generator --> blog_en
@enduml
Thymeleaf ile şablonlama I18n
Mesaj dosyalarının yapısı
İlk adım, her desteklenen dil için özellik dosyalarını oluşturmaktır :
src/jbake/templates/
├── messages.properties # Fallback par défaut
├── messages_fr.properties # Français
├── messages_en.properties # Anglais
└── messages_de.properties # Allemand
Mesaj dosyalarının içeriği
Bir dosya örneği`messages_fr.properties`:
# Navigation
nav.home=Accueil
nav.blog=Blog
nav.about=À propos
nav.contact=Contact
# Articles
article.readmore=Lire la suite
article.published=Publié le
article.tags=Étiquettes
article.also.available=Également disponible en
# Interface
site.title=Mon Blog Technique
site.description=Partage de connaissances et expériences
footer.copyright=© 2025 Tous droits réservés
search.placeholder=Rechercher un article...
Ve onun İngilizce karşılığı`messages_en.properties`:
# Navigation
nav.home=Home
nav.blog=Blog
nav.about=About
nav.contact=Contact
# Articles
article.readmore=Read more
article.published=Published on
article.tags=Tags
article.also.available=Also available in
# Interface
site.title=My Tech Blog
site.description=Sharing knowledge and experiences
footer.copyright=© 2025 All rights reserved
search.placeholder=Search articles...
Şablonlarda kullanım
Thymeleaf şablonlarınızda, sözdizimi kullanın`#{}`mesajlara erişmek için:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{site.title}">Mon Blog</title>
<meta name="description" th:content="#{site.description}" />
</head>
<body>
<nav>
<a th:href="@{/}" th:text="#{nav.home}">Accueil</a>
<a th:href="@{/blog/}" th:text="#{nav.blog}">Blog</a>
<a th:href="@{/about.html}" th:text="#{nav.about}">À propos</a>
</nav>
<footer>
<p th:text="#{footer.copyright}">Copyright</p>
</footer>
</body>
</html>
Dizilim diyagramı - i18n çözümü
@startuml
actor Auteur
participant JBake
participant "AsciiDoc\nAyrıştırıcısı" as parser
participant "Thymeleaf\nMotoru" as thymeleaf
participant "I18n\nÇözümleyici" as i18n
database "messages_*.properties" as messages
Auteur -> JBake: jbake -b
activate JBake
JBake -> parser: Lire article.fr.adoc
activate parser
parser --> JBake: Contenu + métadonnées\n(lang=fr, article-id=xyz)
deactivate parser
JBake -> parser: Lire article.en.adoc
activate parser
parser --> JBake: Contenu + métadonnées\n(lang=en, article-id=xyz)
deactivate parser
JBake -> thymeleaf: Générer page FR
activate thymeleaf
thymeleaf -> i18n: Résoudre #{nav.home}
activate i18n
i18n -> messages: Lire messages_fr.properties
messages --> i18n: "Ana Sayfa"
i18n --> thymeleaf: "Ana Sayfa"
deactivate i18n
thymeleaf -> JBake: Rechercher traductions\n(article-id=xyz, lang!=fr)
JBake --> thymeleaf: article.en.html trouvé
thymeleaf --> JBake: /fr/blog/article.html
deactivate thymeleaf
JBake -> thymeleaf: Générer page EN
activate thymeleaf
thymeleaf -> i18n: Résoudre #{nav.home}
activate i18n
i18n -> messages: Lire messages_en.properties
messages --> i18n: "Ev"
i18n --> thymeleaf: "Ev"
deactivate i18n
thymeleaf -> JBake: Rechercher traductions\n(article-id=xyz, lang!=en)
JBake --> thymeleaf: article.fr.html trouvé
thymeleaf --> JBake: /en/blog/article.html
deactivate thymeleaf
JBake --> Auteur: Site généré
deactivate JBake
@enduml
JBake’te yerel ayar yapılandırması
Dosyanızda`jbake.properties`, varsayılan locale’yi ayarlayın :
# Locale par défaut
thymeleaf.locale=fr
# Encodage
template.encoding=UTF-8
Makalelerin I18n’i: Klasör bazlı organizasyon
akış diyagramı (Flow) - oluşturma
@startuml
start
:Lire jbake.properties;
:Charger configuration i18n;
partition "Her AsciiDoc dosyası için" {
:Lire métadonnées\n(:jbake-lang:, :jbake-article-id:);
:Parser contenu AsciiDoc;
:Stocker en mémoire avec langue;
}
partition "Her şablon için" {
:Charger template Thymeleaf;
:Identifier langue cible;
:Charger messages_{lang}.properties;
partition "İçerik filtreleme" {
:Filtrer articles par langue;
:Grouper traductions\npar article-id;
}
:Appliquer template avec i18n;
:Générer HTML dans /{lang}/;
}
:Copier assets statiques;
:Générer sitemap multilingue;
:Générer flux RSS par langue;
stop
@enduml
Klasör yapısı
Dosya adlarında sonek kullanmak yerine, daha fazla açıklık ve bakım kolaylığı sağlayan klasör tabanlı bir organizasyonu tercih ettim:
content/blog/
├── 2024/
│ ├── fr/
│ │ ├── introduction-jbake.adoc
│ │ ├── guide-thymeleaf.adoc
│ │ └── astuces-asciidoc.adoc
│ └── en/
│ ├── introduction-jbake.adoc
│ ├── thymeleaf-guide.adoc
│ └── asciidoc-tips.adoc
└── 2025/
├── fr/
│ └── internationalisation-jbake.adoc
└── en/
└── jbake-internationalization.adoc
Bu yaklaşımın avantajları
Bu yapı birden fazla avantaja sahiptir :
-
net ayrım: Her dilin kendi alanı var.
-
Esnek adlandırma: dosyaların dila göre farklı isimler olabilir
-
Ölçeklenebilirlik: yeni bir dil eklemek kolay
-
Doğal organizasyonJBake’in zaman mantığını takip eder
Makalelerin meta verileri
Her makale, çeviriler arasında bağlantı kurmak için meta veriler içermelidir. Bir örnek şöyle:
Fransızca sürüm(2025/fr/internationalisation-jbake.adoc) :
= Internationalisation d'un site statique JBake
:jbake-type: post
:jbake-status: published
:jbake-date: 2025-10-20
:jbake-lang: tr
:jbake-article-id: jbake-i18n-thymeleaf
:jbake-tags: jbake, thymeleaf, i18n
:jbake-description: Guide pour mettre en place l'i18n avec JBake
İngilizce sürüm(2025/en/jbake-internationalization.adoc) :
= Internationalizing a JBake Static Site
:jbake-type: post
:jbake-status: published
:jbake-date: 2025-10-20
:jbake-lang: tr
:jbake-article-id: jbake-i18n-thymeleaf
:jbake-tags: jbake, thymeleaf, i18n
:jbake-description: Guide to implement i18n with JBake
Not: Özellik`:jbake-article-id:`kritiktir: bir makalenin farklı çevirileri arasında bağlantı kurar.
URL yapılandırması
içinde`jbake.properties`, URL desenini dil içerecek şekilde yapılandırın:
# Pattern d'URL avec langue
post.permalink.pattern=:lang/blog/:year/:name.html
# Langue par défaut
site.default.lang=fr
Bu şu tür URL’leri oluşturacaktır:
-
/fr/blog/2025/internationalisation-jbake.html -
/en/blog/2025/jbake-internationalization.html
Çoklu dil görüntüleme şablonları
Dil seçici makale şablonu
Şablon oluşturun`post.html`Kullanılabilir çevirileri gösteren :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${content.title}">Article</title>
</head>
<body>
<article>
<header>
<h1 th:text="${content.title}">Titre</h1>
<div class="article-meta">
<time th:text="${#dates.format(content.date, 'dd MMMM yyyy')}"
th:attr="datetime=${#dates.format(content.date, 'yyyy-MM-dd')}">
Date
</time>
<!-- Sélecteur de traductions -->
<div class="translations" th:if="${content['article-id']}">
<span th:text="#{article.also.available}">Aussi disponible en :</span>
<ul class="language-list">
<li th:each="post : ${published_posts}"
th:if="${post['article-id'] == content['article-id'] and post.lang != content.lang}">
<a th:href="${post.uri}"
th:text="${post.lang.toUpperCase()}">
LANG
</a>
</li>
</ul>
</div>
</div>
</header>
<div class="content" th:utext="${content.body}">
Contenu de l'article
</div>
<footer class="article-footer">
<div class="tags" th:if="${content.tags}">
<span th:text="#{article.tags}">Étiquettes :</span>
<span th:each="tag : ${content.tags}">
<a th:href="@{/tags/{tag}.html(tag=${tag})}"
th:text="${tag}">tag</a>
</span>
</div>
</footer>
</article>
</body>
</html>
Dil tarafından filtrelenmiş indeks
Her dil için dizin şablonları oluşturun :
index.html(Fransızca indeks) :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{site.title}">Mon Blog</title>
</head>
<body>
<main>
<h1 th:text="#{nav.blog}">Blog</h1>
<div class="articles-list">
<article th:each="post : ${published_posts}"
th:if="${post.lang == 'fr'}">
<h2>
<a th:href="${post.uri}" th:text="${post.title}">Titre</a>
</h2>
<time th:text="${#dates.format(post.date, 'dd MMMM yyyy')}">
Date
</time>
<p th:text="${post.description}">Description</p>
<a th:href="${post.uri}" th:text="#{article.readmore}">
Lire la suite
</a>
</article>
</div>
</main>
</body>
</html>
index_en.html(İngiliz indeksi) :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{site.title}">My Blog</title>
</head>
<body>
<main>
<h1 th:text="#{nav.blog}">Blog</h1>
<div class="articles-list">
<article th:each="post : ${published_posts}"
th:if="${post.lang == 'en'}">
<h2>
<a th:href="${post.uri}" th:text="${post.title}">Title</a>
</h2>
<time th:text="${#dates.format(post.date, 'dd MMMM yyyy')}">
Date
</time>
<p th:text="${post.description}">Description</p>
<a th:href="${post.uri}" th:text="#{article.readmore}">
Read more
</a>
</article>
</div>
</main>
</body>
</html>
Diller arasında gezinme
Global dil seçici
Akış diyagramı - Kullanıcı okuması
@startuml
start
:Utilisateur accède au site;
if (Langue préférée ?) then (FR)
:Afficher /index.html;
:Lister articles FR;
else (EN)
:Afficher /en/index.html;
:Lister articles EN;
endif
:Utilisateur clique sur article;
:Afficher article\navec métadonnées;
if (Traductions disponibles ?) then (oui)
:Afficher sélecteur\nde traductions;
if (Changement de langue ?) then (oui)
:Rediriger vers\ntraduction;
:Afficher article\ndans nouvelle langue;
else (non)
:Continuer lecture;
endif
else (non)
:Continuer lecture;
endif
stop
@enduml
Ana şablonunuza bir dil seçici ekleyin :
<nav class="language-switcher">
<a href="/index.html"
th:classappend="${content.lang == 'fr'} ? 'active'"
title="Français">
🇫🇷 FR
</a>
<a href="/en/index.html"
th:classappend="${content.lang == 'en'} ? 'active'"
title="English">
🇬🇧 EN
</a>
</nav>
Seçici için CSS stil
.language-switcher {
display: flex;
gap: 1rem;
padding: 0.5rem;
background: #f5f5f5;
border-radius: 4px;
}
.language-switcher a {
padding: 0.5rem 1rem;
text-decoration: none;
color: #333;
border-radius: 4px;
transition: background 0.2s;
}
.language-switcher a:hover {
background: #e0e0e0;
}
.language-switcher a.active {
background: #007bff;
color: white;
}
.translations {
margin: 1rem 0;
padding: 1rem;
background: #f8f9fa;
border-left: 4px solid #007bff;
}
.language-list {
display: inline-flex;
gap: 0.5rem;
list-style: none;
padding: 0;
margin: 0;
}
.language-list li::after {
content: "•";
margin-left: 0.5rem;
}
.language-list li:last-child::after {
content: "";
}
Dil bazlı RSS akışı
Dilara ayrılmış RSS akışları almak için, ayrı şablonlar oluşturun :
feed.xml( Français akım) :
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:th="http://www.thymeleaf.org">
<channel>
<title th:text="#{site.title}">Mon Blog</title>
<link th:text="${config.site_host}">http://example.com</link>
<description th:text="#{site.description}">Description</description>
<language>fr</language>
<item th:each="post : ${published_posts}"
th:if="${post.lang == 'fr'}">
<title th:text="${post.title}">Titre</title>
<link th:text="${config.site_host + post.uri}">Lien</link>
<pubDate th:text="${#dates.format(post.date, 'EEE, dd MMM yyyy HH:mm:ss Z')}">
Date
</pubDate>
<description th:text="${post.description}">Description</description>
</item>
</channel>
</rss>
İyi uygulamalar ve ipuçları
1. Makale kimlik tutarlılığı
Emin olun ki`:jbake-article-id:`Bütün bir makalenin çevirileri için aynıdır. Tutarlı bir format kullanın:
-
Küresellik için İngilizce tanımlayıcıları tercih edin.
-
Kelimeleri ayırmak için tiret kullanın.
-
Özel karakterlerden kaçının
2. Tutarlı tarihler
Bir makalenin tüm çevirileri aynı yayın tarihine sahip olmalıdır.:jbake-date:). Bu, sıralama ve kronolojik görüntülemeyi kolaylaştırır.
3. Çok dilli etiketler
Etiketler için iki seçeneğiniz var:
Seçenek 1: İngilizce evrensel etiketler
:jbake-tags: java, spring-boot, microservices
Seçenek 2: Eşlemeyle çevrilmiş etiketler
# Version française
:jbake-tags: java, spring-boot, microservices
# Version anglaise
:jbake-tags: java, spring-boot, microservices
4. Çevirilmemiş makalelerin yönetimi
Tüm makaleleri çevirmek zorunlu değildir. Bir makale sadece bir dilde mevcutsa, diğer dildeki listelerde görünmez olur.
5. Çok dilli site haritası
Tüm dilleri içeren bir site haritası oluşturun :
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<url th:each="post : ${published_posts}">
<loc th:text="${config.site_host + post.uri}">URL</loc>
<lastmod th:text="${#dates.format(post.date, 'yyyy-MM-dd')}">Date</lastmod>
<!-- Liens alternatifs pour les traductions -->
<xhtml:link th:each="translation : ${published_posts}"
th:if="${translation['article-id'] == post['article-id'] and translation.lang != post.lang}"
rel="alternate"
th:attr="hreflang=${translation.lang},href=${config.site_host + translation.uri}" />
</url>
</urlset>
Sonuç
JBake sitesinin Thymeleaf ile uluslararasılaştırılması, sağlam ve sürdürülebilir bir yaklaşımdır. Şablondan (mesaj dosyaları üzerinden) ve içerikten (klasör organizasyonu üzerinden) i18n’i ayırarak, kolayca gelişebilecek esnek bir sistem elde edersiniz.
Aklınızda bulundurmanız gereken önemli noktalar:
-
Thymeleaf mesaj dosyalarıkullanıcı arayüzü için
-
Dossiye göre düzenleme(yıl/dil) makaleler için
-
ürün kimlikleripour lier les traductions → çevirileri bağlamak
-
özel şablonlarher dil için
-
Açık URL’lerdil kodunu içeren
Dağıtım diyagramı
@startuml
node "Geliştirme makinesi" {
artifact "Kaynaklar" {
folder "memnun/"
folder "şablonlar/"
folder "assets/"
}
component "JBake CLI" as jbake
Sources --> jbake : jbake -b
}
node "Derleme sunucusu\n(CI/CD)" {
component "GitHub Actions\nveya GitLab CI" as ci
jbake --> ci : push
}
cloud "CDN / Barındırma" {
node "Statik Web Sunucusu" {
artifact "Oluşturulan Site" {
folder "/fr/blog/"
folder "/en/blog/"
folder "/assets/"
}
}
}
ci --> "Oluşturulan Site" : déploiement
actor "Okuyucular" as users
users --> "Statik Web Sunucusu" : HTTPS
@enduml
Bu mimari, sadece iki dil ile başlangıç yapmanızı ve büyük bir refaktörleme yapmadan diğer dilleri eklemenizi sağlar. Tüm şey tamamen statik ve performant kalır, JBake felsefesine sadık kalır.
Çok dilli geliştirme iyi geçsin! 🌍