İçindekiler
Rust i18n: Rust Uygulamaları için Uluslararasılaştırma
Rust'ın sistem programlama kökleri, öncelikli olarak düşük seviyeli, yerel ayardan bağımsız kodlar için kullanıldığını düşündürebilir; ancak gerçeklik oldukça farklıdır. Rust; web servisleri, masaüstü uygulamaları, WebAssembly modülleri ve birden fazla dilde global kullanıcılara hizmet etmesi gereken CLI'lar için güç kaynağı olmaktadır.
Rust i18n ekosistemi, pek çok dilin ekosistemine kıyasla daha genç olmakla birlikte, özellikle Mozilla'nın Fluent projesi ve Unicode'un ICU4X girişimi etrafında önemli yatırımlar görmüştür. Bu kılavuz, uluslararasılaştırılmış Rust uygulamaları geliştirmek için başlıca kütüphaneleri, kalıpları ve değiş tokuşları ele almaktadır.
Rust i18n Ekosistemi
| Crate | Amaç |
|---|---|
fluent / fluent-bundle | Mozilla'nın Fluent yerelleştirme sistemi |
fluent-templates | Template engine'ler için Fluent entegrasyonu |
i18n-embed | Derleme zamanı yerel ayar gömme ve çalışma zamanı yükleme |
rust-i18n | JSON/YAML/TOML desteğiyle makro tabanlı i18n |
icu (ICU4X) | Unicode CLDR tabanlı sayı, tarih, çoğul biçimlendirme |
unic-langid | BCP 47 dil tanımlayıcısı ayrıştırma |
accept-language | HTTP Accept-Language başlığı ayrıştırma |
chrono | Tarih/saat işleme (yerel ayardan bağımsız) |
Yaklaşım 1: fluent-bundle ile Fluent
Mozilla'nın Fluent sistemi, gettext ve anahtar-değer string haritalarının sınırlamalarını gidermek için tasarlanmış bir yerelleştirme sistemidir. Fluent, dilbilimsel karmaşıklığı kodunuzdan çıkararak çevirmenlerin bununla ilgilenebileceği çeviri dosyalarına taşır.
Neden Fluent?
Geleneksel yaklaşımlar (t("key")), dilbilimsel kararları geliştiricinin eline bırakır: "Çoğul form için {count, plural, one {item} other {items}} gerekli mi?" Fluent ise bu kararları çevirmene bırakır: çevirmen, kendi dili için doğru çoğul biçimlerini yazar.
Fluent ayrıca şunları destekler:
- Öznitelik mesajları (birden fazla çevrilmiş varyanta sahip tek bir varlık)
- Mesaj referansları (çevrilmiş metni diğer mesajlarda yeniden kullanma)
- Terimler (marka adları gibi paylaşılan, yerelleştirilemeyen tanımlar)
- Seçiciler (değişkenlere, sayılara ve tarihlere dayalı koşullar)
Fluent Sözdizimi Örneği
# en-US/main.ftl
# Basit mesaj
welcome = Welcome to our app!
# Değişkenli mesaj
greeting = Hello, { $name }!
# Çoğul seçimli mesaj
emails =
{ $count ->
[0] You have no new emails.
[one] You have one new email.
*[other] You have { $count } new emails.
}
# Öznitelikli mesaj (etiket + araç ipucu içeren UI öğeleri için)
submit-button =
.label = Submit
.tooltip = Click to submit your form
# Terimler (marka adları, çevrilmez)
-brand-name = Acme Corp
referral = Thank you for using { -brand-name }!
# fr/main.ftl
welcome = Bienvenue dans notre application !
greeting = Bonjour, { $name } !
emails =
{ $count ->
[0] Vous n'avez pas de nouveaux e-mails.
[one] Vous avez un nouvel e-mail.
*[other] Vous avez { $count } nouveaux e-mails.
}
submit-button =
.label = Soumettre
.tooltip = Cliquez pour soumettre votre formulaire
referral = Merci d'utiliser { -brand-name } !
fluent-bundle Kullanımı
use fluent::{FluentBundle, FluentResource};
use fluent_langneg::negotiate_languages;
use unic_langid::LanguageIdentifier;
fn create_bundle(locale: &str, ftl_string: &str) -> FluentBundle<FluentResource> {
let langid: LanguageIdentifier = locale.parse().expect("Invalid locale");
let mut bundle = FluentBundle::new(vec![langid]);
let resource = FluentResource::try_new(ftl_string.to_string())
.expect("Failed to parse FTL");
bundle.add_resource(resource).expect("Failed to add resource");
bundle
}
fn translate_emails(bundle: &FluentBundle<FluentResource>, count: i64) -> String {
let msg = bundle.get_message("emails").expect("Message not found");
let pattern = msg.value().expect("Message has no value");
let mut args = fluent::FluentArgs::new();
args.set("count", count);
let mut errors = vec![];
let value = bundle.format_pattern(pattern, Some(&args), &mut errors);
if !errors.is_empty() {
eprintln!("Fluent errors: {:?}", errors);
}
value.into_owned()
}
fn main() {
let en_ftl = include_str!("../locales/en-US/main.ftl");
let bundle = create_bundle("en-US", en_ftl);
println!("{}", translate_emails(&bundle, 0)); // "You have no new emails."
println!("{}", translate_emails(&bundle, 1)); // "You have one new email."
println!("{}", translate_emails(&bundle, 5)); // "You have 5 new emails."
}
Yaklaşım 2: rust-i18n (Makro Tabanlı)
Daha basit uygulamalar veya tanıdık t!() makro yaklaşımını tercih eden geliştiriciler için rust-i18n, YAML çeviri dosyalarıyla desteklenen ergonomik bir arayüz sunar.
Kurulum
# Cargo.toml [dependencies] rust-i18n = "3" [package.metadata.i18n] available-locales = ["en", "fr", "de", "ja"] default-locale = "en" load-path = "locales"
Çeviri Dosyaları
# locales/en.yaml
welcome: "Welcome!"
greeting: "Hello, %{name}!"
items:
zero: "No items"
one: "One item"
other: "%{count} items"
# locales/fr.yaml
welcome: "Bienvenue !"
greeting: "Bonjour, %{name} !"
items:
zero: "Aucun élément"
one: "Un élément"
other: "%{count} éléments"
t! Makrosunun Kullanımı
use rust_i18n::t;
// i18n'i başlat (derleme zamanında yerel ayar dosyalarını yükler)
rust_i18n::i18n!("locales");
fn main() {
// Aktif yerel ayarı belirle
rust_i18n::set_locale("fr");
// Basit çeviri
println!("{}", t!("welcome")); // "Bienvenue !"
// Değişken enterpolasyonuyla
println!("{}", t!("greeting", name = "Alice")); // "Bonjour, Alice !"
// Çoğul işleme
for count in [0, 1, 5] {
println!("{}", t!("items", count = count));
}
}
Web Handler'da Yerel Ayar Kullanımı (Axum)
use axum::{
extract::{Extension, TypedHeader},
headers::AcceptLanguage,
response::Json,
};
use rust_i18n::t;
use serde_json::json;
async fn greet_handler(
TypedHeader(accept_language): TypedHeader<AcceptLanguage>,
Extension(state): Extension<AppState>,
) -> Json<serde_json::Value> {
// Accept-Language'ı ayrıştır ve en uygun yerel ayarı seç
let locale = negotiate_locale(&accept_language, &["en", "fr", "de"]);
// Bu istek için yerel ayarı belirle (not: rust-i18n'de bu thread-local'dır)
rust_i18n::set_locale(&locale);
Json(json!({
"message": t!("welcome"),
"locale": locale,
}))
}
fn negotiate_locale(accept: &AcceptLanguage, supported: &[&str]) -> String {
for quality_value in accept.iter() {
let lang = quality_value.item.to_string();
let prefix = lang.split('-').next().unwrap_or(&lang);
if supported.contains(&prefix) {
return prefix.to_string();
}
}
"en".to_string()
}
Yerel Ayara Duyarlı Biçimlendirme için ICU4X
ICU4X, Rust'ta CLDR tabanlı uluslararasılaştırma ilkellerini sağlayan bir Unicode projesidir. Sayı biçimlendirme, tarih/saat biçimlendirme, çoğul kurallar ve daha fazlasını; Unicode'un Common Locale Data Repository (CLDR) veri kaynağını kullanarak yönetir.
Sayı Biçimlendirme
use icu::decimal::FixedDecimalFormatter;
use icu::locid::locale;
use fixed_decimal::FixedDecimal;
fn format_number(amount: f64, locale_str: &str) -> String {
// Not: ICU4X, derlenmiş yerel ayarların sabit bir kümesini destekler
// Dinamik yerel ayar seçimi için data provider kullanın
let locale = locale!("de"); // Almanca yerel ayar
let fdf = FixedDecimalFormatter::try_new(
&locale.into(),
Default::default(),
).expect("locale data should be present");
let decimal = FixedDecimal::from(amount as i64);
fdf.format_to_string(&decimal)
}
Çoğul Kurallar
use icu::plurals::{PluralRules, PluralRuleType, PluralCategory};
use icu::locid::locale;
fn get_plural_category(count: f64, locale_str: &str) -> PluralCategory {
// İngilizce çoğul kuralları
let locale = locale!("en");
let pr = PluralRules::try_new(
&locale.into(),
PluralRuleType::Cardinal,
).expect("locale data should be present");
pr.category_for(FixedDecimal::from(count as i64))
}
fn translate_items(count: i64, locale: &str) -> &'static str {
let category = get_plural_category(count as f64, locale);
match (locale, category) {
("en", PluralCategory::One) => "1 item",
("en", _) if count == 0 => "No items",
("en", _) => "items", // yer tutucu, gerçek uygulama biçimlendirirdi
_ => "items",
}
}
Çeviri Dosyalarını Çalışma Zamanında ve Derleme Zamanında Yükleme
Rust, derleme zamanı ve çalışma zamanı çeviri yüklemesi arasında tercih yapmanıza olanak tanır:
Derleme Zamanı Gömme (CLI için Önerilen)
use std::collections::HashMap;
// Çeviri dosyalarını derleme zamanında göm
static EN_TRANSLATIONS: &str = include_str!("../locales/en.json");
static FR_TRANSLATIONS: &str = include_str!("../locales/fr.json");
fn load_translations() -> HashMap<String, HashMap<String, String>> {
let mut translations = HashMap::new();
translations.insert(
"en".to_string(),
serde_json::from_str(EN_TRANSLATIONS).expect("Valid JSON"),
);
translations.insert(
"fr".to_string(),
serde_json::from_str(FR_TRANSLATIONS).expect("Valid JSON"),
);
translations
}
Derleme zamanı gömme, yerel ayar dosyalarına çalışma zamanı bağımlılığı olmayan tek bir ikili dosya üretir. Bu, CLI araçları ve gömülü uygulamalar için idealdir.
Çalışma Zamanı Yükleme (Web Servisleri için Önerilen)
use std::collections::HashMap;
use std::path::Path;
fn load_translations_from_dir(dir: &Path) -> HashMap<String, HashMap<String, String>> {
let mut translations = HashMap::new();
for entry in std::fs::read_dir(dir).expect("locale dir exists") {
let entry = entry.expect("valid dir entry");
let path = entry.path();
if path.extension().map_or(false, |ext| ext == "json") {
let locale = path
.file_stem()
.expect("file has stem")
.to_string_lossy()
.to_string();
let content = std::fs::read_to_string(&path)
.expect("locale file readable");
let strings: HashMap<String, String> =
serde_json::from_str(&content).expect("valid JSON");
translations.insert(locale, strings);
}
}
translations
}
Çalışma zamanı yükleme, yeniden derleme yapmadan çevirilerin güncellenmesine olanak tanır; çevirilerin sık güncellendiği web servisleri için kullanışlıdır.
WebAssembly ve Rust i18n
Rust, WebAssembly'ye derlenir ve Rust i18n kütüphaneleri bazı kısıtlamalarla WASM ortamında çalışır:
- Tarayıcı WASM ortamlarında dosya sistemi erişimi mevcut değildir
- Derleme zamanı gömme (
include_str!) WASM için iyi çalışır - ICU4X, WASM dahil kısıtlı ortamlarda çalışmak üzere özel olarak tasarlanmıştır
#[cfg(target_arch = "wasm32")]
mod wasm {
use wasm_bindgen::prelude::*;
use rust_i18n::t;
rust_i18n::i18n!("locales");
#[wasm_bindgen]
pub fn translate(key: &str, locale: &str) -> String {
rust_i18n::set_locale(locale);
t!(key)
}
}
Rust CLI'lar için i18n
CLI uygulamaları, sistem yerel ayarını ortam değişkenlerinden tespit eder:
use std::env;
pub fn detect_system_locale() -> String {
// Öncelik: LANG > LC_ALL > LC_MESSAGES > varsayılan
for var in &["LANG", "LC_ALL", "LC_MESSAGES"] {
if let Ok(val) = env::var(var) {
// LANG genellikle "en_US.UTF-8" biçimindedir
let locale = val
.split('.') // Kodlama sonekini kaldır
.next()
.unwrap_or("en")
.replace('_', "-"); // BCP 47'ye dönüştür (en_US → en-US)
if !locale.is_empty() {
return locale;
}
}
}
// Windows: windows crate aracılığıyla GetUserDefaultLocaleName kullan
#[cfg(target_os = "windows")]
{
// ... Windows yerel ayar tespiti
}
"en".to_string()
}
Rust i18n Testi
#[cfg(test)]
mod tests {
use rust_i18n::t;
#[test]
fn test_english_plurals() {
rust_i18n::set_locale("en");
// Tüm çoğul biçimleri test et
assert_eq!(t!("items", count = 0), "No items");
assert_eq!(t!("items", count = 1), "One item");
assert_eq!(t!("items", count = 5), "5 items");
}
#[test]
fn test_french_translation() {
rust_i18n::set_locale("fr");
assert_eq!(t!("welcome"), "Bienvenue !");
assert_eq!(t!("greeting", name = "Marie"), "Bonjour, Marie !");
}
#[test]
fn test_fallback_to_english() {
// Çeviri dosyası olmayan yerel ayar, İngilizce'ye geri döner
rust_i18n::set_locale("xx");
assert_eq!(t!("welcome"), "Welcome!"); // İngilizce geri dönüş
}
}
Rust için geçerli kapsamlı test stratejileri için bkz. i18n test araçları, stratejileri ve otomasyon.
Fluent ve rust-i18n Arasında Seçim
| Kriter | Fluent | rust-i18n |
|---|---|---|
| Dilbilimsel ifade gücü | Yüksek | Orta |
| Çevirmen dostu | Çok | Orta |
| Geliştirici ergonomisi | Daha ayrıntılı | Basit makrolar |
| Çoğul işleme | FTL'de yerel | Anahtar sonekleri |
| Topluluk benimsemesi | Büyüyor | Daha fazla benimseme |
| En iyi kullanım | Karmaşık uygulamalar, topluluk çevirisi | Daha basit uygulamalar, geliştirici ekipleri |
Karmaşık dilbilimsel ihtiyaçları olan veya büyük çevirmen topluluklarına sahip uygulamalar için Fluent'in ifade gücü, ek kurulum zahmetine değer. Daha basit uygulamalar veya geliştirici yönetimli çeviriler için rust-i18n'nin ergonomisi öne çıkar.
Uygulamanızı better-i18n ile dünyaya açın
better-i18n; yapay zeka destekli çeviriler, git-native iş akışları ve global CDN dağıtımını tek bir geliştirici odaklı platformda bir araya getirir. Elektronik tablolar yönetmeyi bırakın, her dilde yayın yapmaya başlayın.
Ücretsiz başlayın → · Özellikleri keşfedin · Belgeleri okuyun