Çok Dilli Eklenti: WordPress Çeviri Dosyaları Rehberi
WordPress eklentisi geliştirirken “şu an sadece Türkçe kullananlar için yapıyorum, çeviri sonra düşünürüm” dediğinizde, sonra hiç gelmez. Bunu onlarca projede gördüm. Altı ay sonra eklentiniz WordPress.org deposuna kabul edildi, uluslararası kullanıcılar geliyor ama kodun yarısı hardcode Türkçe string dolu. İşte o anda gerçek ağrı başlıyor. Bu yazıda, WordPress çeviri altyapısını en başından doğru kurmayı, .pot, .po, .mo dosyalarını yönetmeyi ve gerçek projelerden öğrendiğim pratik püf noktalarını aktaracağım.
WordPress Çeviri Sistemi Neden Bu Kadar Önemli
WordPress’in i18n (internationalization) sistemi, aslında oldukça köklü bir GNU gettext altyapısına dayanıyor. Ama çoğu geliştirici bu altyapıyı yüzeysel anlıyor ve sonra “çeviri dosyam neden yüklenmiyor?” diye saatlerce debug yapıyor.
Temel mantık şu: kodunuzda stringleri doğrudan yazmak yerine, onları özel fonksiyonlara sarıyorsunuz. Bu fonksiyonlar çalışma zamanında ilgili dile ait çeviriyi arıyor, bulamazsa orijinal metni gösteriyor. Yani sistem, graceful degradation yapıyor. Çeviriniz eksikse uygulama kırılmıyor, sadece orijinal dilde kalıyor.
Bir eklenti geliştirirken karşılaşacağınız dosya türleri:
- .pot dosyası: Portable Object Template. Tüm çevrilebilir stringlerin şablonu. Kaynak dosya bu.
- .po dosyası: Portable Object. Belirli bir dil için çevirileri içeren, insan tarafından okunabilir metin dosyası.
- .mo dosyası: Machine Object.
.podosyasının derlenmiş, binary versiyonu. WordPress bu dosyayı okur. - .json dosyası: JavaScript tarafındaki stringleri block editor (Gutenberg) için kullanılan format.
Eklenti Yapısını Doğru Kurmak
Önce klasör yapısını konuşalım. Languages dosyalarınızı nereye koyduğunuz kritik. WordPress’in beklediği standart yapı şu şekilde:
my-plugin/
├── my-plugin.php
├── includes/
├── assets/
└── languages/
├── my-plugin.pot
├── my-plugin-tr_TR.po
├── my-plugin-tr_TR.mo
└── my-plugin-de_DE.po
Ana eklenti dosyasında load_plugin_textdomain fonksiyonunu mutlaka init hook’una bağlayın. Pek çok geliştirici bunu plugins_loaded hook’una bağlıyor, ikisi de çalışıyor ama init daha güvenli çünkü bazı tema ve eklentiler erken çevirilere ihtiyaç duyuyor.
<?php
/**
* Plugin Name: My Awesome Plugin
* Plugin URI: https://example.com
* Text Domain: my-plugin
* Domain Path: /languages
* Version: 1.0.0
*/
function my_plugin_load_textdomain() {
load_plugin_textdomain(
'my-plugin',
false,
dirname( plugin_basename( __FILE__ ) ) . '/languages/'
);
}
add_action( 'init', 'my_plugin_load_textdomain' );
Dikkat edilecek kritik nokta: plugin header’daki Text Domain değeri ile load_plugin_textdomain‘e geçtiğiniz ilk parametre birebir aynı olmalı. Büyük/küçük harf dahil. Bunu tutarsız yapan onlarca eklenti gördüm, sonra neden çalışmadığını anlayamıyorlar.
Stringleri Doğru İşaretlemek
Kodunuzdaki tüm kullanıcıya gösterilen metinleri çeviri fonksiyonlarıyla sarmanız gerekiyor. WordPress’in sunduğu ana fonksiyonlar:
__( 'Text', 'textdomain' ): String döndürür, echo etmez._e( 'Text', 'textdomain' ): String’i doğrudan ekrana basar._n( 'singular', 'plural', $count, 'textdomain' ): Tekil/çoğul için._x( 'Text', 'context', 'textdomain' ): Bağlam bilgisiyle birlikte. Aynı kelime farklı bağlamlarda farklı çevrilmesi gerektiğinde kullanılır.esc_html__( 'Text', 'textdomain' ): Çevirir ve HTML escape uygular. Frontend’e çıkan değerlerde tercih edin.esc_attr__( 'Text', 'textdomain' ): HTML attribute değerleri için.
Gerçek bir admin sayfası örneği üzerinden gösterelim:
<?php
function my_plugin_admin_page() {
$saved_count = get_option( 'my_plugin_count', 0 );
?>
<div class="wrap">
<h1><?php esc_html_e( 'Eklenti Ayarları', 'my-plugin' ); ?></h1>
<p>
<?php
printf(
/* translators: %d: number of items processed */
esc_html( _n(
'%d öğe işlendi.',
'%d öğe işlendi.',
$saved_count,
'my-plugin'
) ),
$saved_count
);
?>
</p>
<label for="my-setting">
<?php esc_html_e( 'Ayar Değeri', 'my-plugin' ); ?>
</label>
<input
type="text"
id="my-setting"
placeholder="<?php esc_attr_e( 'Bir değer girin...', 'my-plugin' ); ?>"
/>
<button class="button button-primary">
<?php esc_html_e( 'Kaydet', 'my-plugin' ); ?>
</button>
</div>
<?php
}
Burada printf ile çeviri kombinasyonu önemli. Değişken içeren cümleleri asla string birleştirme ile kurmayın. "Merhaba " . $name . ", hoş geldiniz" gibi bir yapı çevrilemez, çünkü farklı dillerde cümle yapısı değişir. printf( __( 'Merhaba %s, hoş geldiniz', 'my-plugin' ), $name ) yapısı hem çevrilebilir hem de dil yapısına uyum sağlar.
.pot Dosyası Oluşturmak: WP-CLI ile Profesyonel Yaklaşım
Manuel olarak .pot dosyası oluşturmak hem zaman kaybı hem de hata kaynağı. WP-CLI’nin wp i18n komutu bu işi otomatize ediyor ve son derece güçlü.
Önce WP-CLI’nin güncel olduğundan emin olun:
wp --info
wp cli update
Şimdi eklentinizin .pot dosyasını oluşturalım:
wp i18n make-pot /var/www/html/wp-content/plugins/my-plugin
/var/www/html/wp-content/plugins/my-plugin/languages/my-plugin.pot
--domain=my-plugin
--exclude=vendor,node_modules,tests
--headers='{"Report-Msgid-Bugs-To":"https://wordpress.org/support/plugin/my-plugin","POT-Creation-Date":"2024-01-15T10:00:00+00:00","PO-Revision-Date":"2024-01-15T10:00:00+00:00","Last-Translator":"Your Name <[email protected]>","Language-Team":"Turkish <[email protected]>"}'
Bu komut PHP dosyalarınızı, JavaScript dosyalarınızı (Gutenberg block’ları dahil) tarayarak tüm çevirilebilir stringleri bulur. Vendor klasörünü hariç tutmayı unutmayın, yoksa Composer bağımlılıklarındaki stringler de dahil olur.
.pot dosyasını commit’lemeniz gerekiyor mu? Evet. Bu dosya kaynak kontrolünde olmalı. Her feature branch’inde stringlere ekleme yapıldıysa .pot dosyasını güncellemeyi CI pipeline’ına dahil etmeyi düşünebilirsiniz.
.po Dosyasını Düzenlemek ve .mo Derleme
.pot dosyası hazır olduğunda, her dil için bir .po dosyası oluşturuyorsunuz. Türkçe için:
# .pot dosyasını kopyalayıp tr_TR.po olarak hazırlama
cp languages/my-plugin.pot languages/my-plugin-tr_TR.po
.po dosyasının içi şöyle görünür:
# Dosyanin basligi bu sekilde duzenleyin
msgid ""
msgstr ""
"Project-Id-Version: My Plugin 1.0.0n"
"Language: tr_TRn"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n"
"Content-Transfer-Encoding: 8bitn"
"Plural-Forms: nplurals=2; plural=(n != 1);n"
msgid "Plugin Settings"
msgstr "Eklenti Ayarları"
msgid "Save"
msgstr "Kaydet"
#. translators: %d: number of items processed
#, php-format
msgid "%d item processed."
msgid_plural "%d items processed."
msgstr[0] "%d öğe işlendi."
msgstr[1] "%d öğe işlendi."
.po dosyasını düzenlemenin en iyi yolu Poedit uygulaması. Ücretsiz versiyonu işinizi görür, ancak profesyonel ekipler için GlotPress (WordPress.org’un kullandığı platform) veya Lokalise, Crowdin gibi SaaS çözümleri de var.
.po hazır olduğunda .mo derleme:
# WP-CLI ile tum .po dosyalarini .mo'ya derle
wp i18n make-mo /var/www/html/wp-content/plugins/my-plugin/languages/
# Veya msgfmt komutuyla (gettext paketi gerekli)
msgfmt -o languages/my-plugin-tr_TR.mo languages/my-plugin-tr_TR.po
Geliştirme ortamınızda gettext araçlarını kurmak için:
# Ubuntu/Debian
sudo apt-get install gettext
# CentOS/RHEL
sudo yum install gettext
# macOS
brew install gettext
JavaScript ve Gutenberg Block Çevirileri
Bu kısım çoğu yazıda atlanıyor ama modern WordPress geliştirmede kritik. Gutenberg block’ları ve JavaScript ile yazılmış admin sayfaları için ayrı bir süreç var.
JavaScript tarafında @wordpress/i18n paketini kullanıyorsunuz:
import { __, _n, sprintf } from '@wordpress/i18n';
// Basit kullanim
const buttonLabel = __( 'Settings', 'my-plugin' );
// Degiskenli kullanim
const message = sprintf(
/* translators: %s: user name */
__( 'Welcome back, %s!', 'my-plugin' ),
userName
);
// Tekil/cogul
const itemCount = sprintf(
_n( '%d item found', '%d items found', count, 'my-plugin' ),
count
);
Bu JavaScript dosyaları için .json çeviri dosyaları oluşturmanız gerekiyor. WP-CLI bunu da yapıyor:
# Once .pot olustur (JS dosyalarini da tarar)
wp i18n make-pot /var/www/html/wp-content/plugins/my-plugin
languages/my-plugin.pot
--domain=my-plugin
# .po dosyasindan JSON olustur
wp i18n make-json languages/my-plugin-tr_TR.po
--no-purge
Bu komut my-plugin-tr_TR-{hash}.json formatında dosyalar oluşturur. Bu dosyaları wp_set_script_translations() ile kayıt etmeniz gerekiyor:
<?php
function my_plugin_register_scripts() {
wp_register_script(
'my-plugin-block-editor',
plugins_url( 'build/index.js', __FILE__ ),
[ 'wp-blocks', 'wp-i18n', 'wp-element' ],
'1.0.0',
true
);
wp_set_script_translations(
'my-plugin-block-editor',
'my-plugin',
plugin_dir_path( __FILE__ ) . 'languages/'
);
}
add_action( 'enqueue_block_editor_assets', 'my_plugin_register_scripts' );
Çeviri Yüklenmiyor mu? Debug Etme Yöntemleri
En sık karşılaşılan sorun: her şeyi doğru yaptım, çeviri yüklenmiyor. İşte sistematik debug yaklaşımı.
Önce temel kontroller:
<?php
// Bu kodu gecici olarak eklentinize ekleyin, debug sonrasi kaldirin
add_action( 'init', function() {
$locale = get_locale();
$text_domain = 'my-plugin';
$plugin_languages_dir = plugin_dir_path( __FILE__ ) . 'languages/';
$mo_file = $plugin_languages_dir . $text_domain . '-' . $locale . '.mo';
error_log( 'Current locale: ' . $locale );
error_log( 'Looking for MO file: ' . $mo_file );
error_log( 'MO file exists: ' . ( file_exists( $mo_file ) ? 'YES' : 'NO' ) );
}, 1 );
Sık yapılan hatalar ve çözümleri:
- Dosya adı yanlış:
my-plugin_tr_TR.moyerinemy-plugin-tr_TR.moolmalı (tire, alt çizgi değil) - Text domain tutarsızlığı: Plugin header,
load_plugin_textdomainve string fonksiyonlarındaki domain aynı olmalı - Hook zamanlaması:
load_plugin_textdomainçok geç çağrılıyor olabilir .modosyası güncel değil:.pogüncellendi ama.moderlenmedi- WordPress dil ayarı:
wp-config.php‘deWPLANGtanımlı değil veya yanlış
# wp-config.php'de dil ayarini kontrol et
grep -n "WPLANG|WP_LANG" /var/www/html/wp-config.php
# Eger tanimli degilse ekle
define( 'WPLANG', 'tr_TR' );
# Daha guvenilir yol: WP-CLI ile kontrol
wp option get WPLANG
wp option update WPLANG tr_TR
Üretim Ortamında Çeviri Dosyası Güncelleme Stratejisi
WordPress.org deposunda yayınlanmış bir eklentiniz varsa, GlotPress üzerinden gelen topluluk çevirilerini nasıl yöneteceksiniz? Kullanıcılar wp-content/languages/plugins/ dizinine çevirilerini indirir, sizin eklenti içindeki çevirilerinizi geçersiz kılabilir. Bu WordPress’in tasarım gereği.
Kendi sunucunuzda eklentiyi yönetiyorsanız ve çeviri güncelleme sürecini otomatize etmek istiyorsanız:
#!/bin/bash
# update-translations.sh
# Bu scripti cron'a ekleyerek aylik calistirabilirsiniz
PLUGIN_DIR="/var/www/html/wp-content/plugins/my-plugin"
LANG_DIR="$PLUGIN_DIR/languages"
WP_CLI="/usr/local/bin/wp"
WP_PATH="/var/www/html"
echo "Çeviri güncelleme başladı: $(date)"
# .pot dosyasini yenile
$WP_CLI i18n make-pot "$PLUGIN_DIR"
"$LANG_DIR/my-plugin.pot"
--domain=my-plugin
--path="$WP_PATH"
--allow-root
# Mevcut .po dosyalarini guncelle (yeni stringleri ekle, eskilerini koru)
for po_file in "$LANG_DIR"/*.po; do
if [ -f "$po_file" ]; then
echo "Güncelleniyor: $po_file"
msgmerge --update --no-fuzzy-matching "$po_file" "$LANG_DIR/my-plugin.pot"
# .mo dosyasini derle
mo_file="${po_file%.po}.mo"
msgfmt -o "$mo_file" "$po_file"
echo "Derlendi: $mo_file"
fi
done
echo "Çeviri güncelleme tamamlandı: $(date)"
Bu scripti cron’a ekleyin:
chmod +x /usr/local/bin/update-translations.sh
# Her ay 1'inde sabah 3'te calistir
echo "0 3 1 * * www-data /usr/local/bin/update-translations.sh >> /var/log/translation-update.log 2>&1" | crontab -
Çeviri Kalitesini Kontrol Etme
String’lerinizi işaretlediniz, çeviriler geldi, ama kalite nasıl? Birkaç araç:
# msgfmt ile .po dosyasini validate et
msgfmt --check --statistics languages/my-plugin-tr_TR.po
# Cevrilmemis stringleri bul
msgattrib --untranslated languages/my-plugin-tr_TR.po
# Fuzzy (belirsiz) ceviri olan stringleri listele
msgattrib --only-fuzzy languages/my-plugin-tr_TR.po
# Fuzzy ceviri isaretlerini kaldirmak (elle onayladiktan sonra)
msgattrib --no-fuzzy languages/my-plugin-tr_TR.po -o languages/my-plugin-tr_TR.po
fuzzy işareti, msgmerge komutu tarafından kaynak string değiştiğinde eklenir. Bu, çevirinin güncellenmiş kaynak string için doğru olmayabileceğini gösterir. Bu işaretli entryleri elle gözden geçirin.
CI/CD Pipeline’ına Entegrasyon
GitHub Actions ile çeviri workflow’u:
# .github/workflows/translations.yml
name: Update Translations
on:
push:
branches: [ main ]
paths:
- '**.php'
- '**.js'
jobs:
update-pot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install WP-CLI
run: |
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
- name: Generate POT file
run: |
wp i18n make-pot . languages/my-plugin.pot
--domain=my-plugin
--exclude=vendor,node_modules
--allow-root
- name: Compile MO files
run: |
sudo apt-get install -y gettext
for po in languages/*.po; do
msgfmt -o "${po%.po}.mo" "$po"
done
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "chore: update translation files"
file_pattern: "languages/*.pot languages/*.mo"
Sonuç
WordPress çeviri sistemi ilk bakışta karmaşık görünüyor, .pot, .po, .mo, .json… ama mantığını kavradıktan sonra oldukça tutarlı bir yapı. Önemli olan bunu projenin başından kurgulamak.
Özetlemek gerekirse:
- Baştan doğru yapıyı kurun: Text domain, dosya adları ve hook zamanlamasına dikkat edin.
- Stringleri doğru sarın: Hardcode string bırakmayın, değişkenlerde
printfkombinasyonunu kullanın. - WP-CLI’yi kullanın: Manuel işlem yapmayın,
wp i18nkomutları güvenilir ve hızlı. - JavaScript’i unutmayın: Gutenberg ve JS admin sayfaları için ayrı JSON çevirileri gerekiyor.
- Debug sürecini sistematik yapın: Locale, dosya yolu ve isim tutarlılığını kontrol edin.
- CI pipeline’ına dahil edin: Her kod değişikliğinde
.potgüncellemesi ve.moderlemesi otomatik olsun.
Çok dilli eklenti geliştirmek, teknik borcu en baştan sıfırda tutmanın en güzel örneklerinden biri. İki saat harcayıp i18n altyapısını doğru kurduğunuzda, ileride saatlerce debug yapmaktan kurtuluyorsunuz. Bu hesabı her zaman yapın.
