En İyi Uygulama Çok Dilli Web Sitesi


179

Bu soruyla birkaç aydır mücadele ediyorum, ancak daha önce tüm olası seçenekleri araştırmam gereken bir durumda değildim. Şu anda, olasılıkları öğrenmenin ve yaklaşan projelerimde kullanmak için kendi kişisel tercihimi yaratmanın zamanı geldiğini hissediyorum.

Önce aradığım durumu çizeyim

Uzun süredir kullandığım bir içerik yönetim sistemini yükseltmek / yeniden geliştirmek üzereyim. Ancak, çoklu dilin bu sistem için büyük bir gelişme olduğunu hissediyorum. Daha önce hiçbir çerçeve kullanmadım ama yaklaşan proje için Laraval4'ü kullanacağım. Laravel, PHP'yi kodlamanın daha temiz bir yolu olarak görünüyor. Sidenote: Laraval4 should be no factor in your answer. Platformdan / çerçeveden bağımsız genel çeviri yollarını arıyorum.

Ne tercüme edilmeli

Aradığım sistemin mümkün olduğunca kullanıcı dostu olması gerektiğinden, çeviriyi yönetme yöntemi CMS içinde olmalıdır. Çeviri dosyalarını veya html / php çözümlü şablonları değiştirmek için bir FTP bağlantısı başlatmaya gerek yoktur.

Ayrıca, belki de ek tablolar yapmak gerek kalmadan birden fazla veritabanı tabloları çevirmek için en kolay yolu arıyorum.

Kendime ne buldum

Kendimi araştırırken, okurken ve denerken zaten. Sahip olduğum birkaç seçenek var. Ama hala gerçekten aradığım şey için en iyi uygulama yöntemine ulaştığımı düşünmüyorum. Şu anda, ben bu kadar geldim, ama bu yöntemin de yan etkileri var.

  1. PHP Ayrıştırılmış Şablonlar : şablon sistemi PHP tarafından ayrıştırılmalıdır. Bu şekilde, çevrilmiş parametreleri HTML'ye şablonları açıp değiştirmek zorunda kalmadan ekleyebilirim. Bunun yanı sıra, PHP ayrıştırılmış şablonları bana her dil için bir alt klasör (daha önce sahip olduğum) yerine bir web sitesi için 1 şablon var yeteneği verir. Bu hedefe ulaşma yöntemi Smarty, TemplatePower, Laravel's Blade veya başka bir şablon ayrıştırıcı olabilir. Dediğim gibi bu yazılı çözümden bağımsız olmalıdır.
  2. Veritabanına Dayalı : Belki bundan tekrar bahsetmem gerekmiyor. Ancak çözüm veritabanına dayalı olmalıdır. CMS nesne odaklı ve MVC olması amaçlanmıştır, bu yüzden dizeleri için mantıklı bir veri yapısı düşünmek gerekir. Benim şablonları yapılanmış olmalıdır gibi: şablonları / Kontrolör / View.php belki de bu yapının en mantıklı olur: Controller.View.parameter. Veritabanı tablosunda bu alanlar bir valuealanla uzun olur . Şablonların içinde gibi bir sıralama yöntemi kullanabiliriz echo __('Controller.View.welcome', array('name', 'Joshua'))ve parametre içerir Welcome, :name. Böylece sonuç Welcome, Joshua. Bu, bunu yapmak için iyi bir yol gibi görünüyor, çünkü: name gibi parametrelerin editör tarafından anlaşılması kolaydır.
  3. Düşük Veritabanı Yükü : Elbette yukarıdaki sistem, bu dizeler hareket halindeyken yüklüyse bir sürü veritabanı yüküne neden olur. Bu nedenle yönetim ortamında düzenlendikleri / kaydedildikleri anda dil dosyalarını yeniden işleyen bir önbellek sistemine ihtiyacım olacaktı. Dosyalar üretildiğinden, iyi bir dosya sistemi düzeni de gereklidir. Sanırım languages/en_EN/Controller/View.phpsize en uygun olan veya .ini ile gidebiliriz . Belki bir .ini sonunda daha hızlı ayrıştırılır. Bu, içindeki verileri içermelidir format parameter=value; . Sanırım bu bunu yapmanın en iyi yolu, çünkü oluşturulan her Görünüm, varsa kendi dil dosyasını içerebilir. Daha sonra, dil parametrelerinin birbirlerinin üzerine yazmasını önlemek için dil parametreleri genel bir görünüme değil belirli bir görünüme yüklenmelidir.
  4. Veritabanı Tablosu çevirisi : bu aslında en çok endişelendiğim şey. Haberler / Sayfalar / vb. Çeviriler oluşturmanın bir yolunu arıyorum. olabildiğince çabuk. Her modül için iki tabloya sahip olmak (örneğin Newsve News_translations) bir seçenektir, ancak iyi bir sistem elde etmek için çok çalışmak gibi hissettirir. Bir dayanmaktadır ile geldi şeylerden biri data versioningYazdığım sistemde: Bir veritabanı tablo adı var Translations, bu tablo benzersiz bir kombinasyonu vardır language, tablenameveprimarykey. Örneğin: en_En / News / 1 (Haber öğesinin ID = 1 olan İngilizce sürümüne bakıldığında). Ancak bu yöntemin 2 büyük dezavantajı vardır: her şeyden önce bu tablo, veritabanında çok fazla veri ile oldukça uzun olma eğilimindedir ve ikincisi, tabloyu aramak için bu kurulumu kullanmak bir iş cehennemi olacaktır. Örneğin öğenin SEO slug aramak oldukça aptal bir tam metin arama olacaktır. Ancak öte yandan: bu, her tabloda çok hızlı bir şekilde çevrilebilir içerik oluşturmanın hızlı bir yoludur, ancak bu profesyonelin konilerin ağırlığını aştığına inanmıyorum.
  5. Ön Uç Çalışması : Ayrıca ön uç biraz düşünmeye ihtiyaç duyacaktır. Tabii ki mevcut dilleri bir veritabanında saklayacağız ve (de) ihtiyacımız olanları aktifleştireceğiz. Bu şekilde komut dosyası bir dil seçmek için bir açılır liste oluşturabilir ve arka uç otomatik olarak CMS kullanılarak hangi çevirilerin yapılabileceğine karar verebilir. Seçilen dil (ör. En_EN), bir görünüm için dil dosyasını alırken veya web sitesindeki bir içerik öğesi için doğru çeviriyi alırken kullanılır.

Yani, işte buradalar. Fikirlerim şimdiye kadar. Henüz tarihler için yerelleştirme seçeneklerini bile içermiyorlar, ancak sunucum PHP5.3.2 + 'ı desteklediğinden, en iyi seçenek intl uzantısını burada açıklandığı gibi kullanmaktır: http://devzone.zend.com/1500/internationalization-in -php-53 / - ama bu daha sonraki gelişim stadyumlarında kullanılabilir. Şimdilik ana sorun, bir web sitesindeki içeriğin çevirisi için en iyi uygulamalara nasıl sahip olacağınızdır.

Burada açıkladığım her şeyin yanı sıra, henüz karar vermediğim başka bir şeyim var, basit bir soruya benziyor, ama aslında bana baş ağrısı veriyor:

URL Çevirisi? Bunu yapmalı mıyız yapmamalı mıyız? ve ne şekilde?

Yani .. bu url varsa: http://www.domain.com/about-usve İngilizce benim varsayılan dilim. Kendi http://www.domain.com/over-onsdilim olarak Hollandaca'yı seçtiğimde bu URL'nin çevrilmesi gerekir mi? Ya da kolay yoldan gidip sayfanın içeriğini görünür durumda değiştirmemiz gerekir /about. Sonuncusu geçerli bir seçenek gibi görünmüyor, çünkü bu aynı URL'nin birden çok sürümünü üretecek, içeriği dizine ekleme doğru şekilde başarısız olacaktır.

http://www.domain.com/nl/about-usBunun yerine başka bir seçenek kullanıyor . Bu, her içerik için en az benzersiz bir URL oluşturur. Ayrıca, bu başka bir dile gitmek daha kolay olurdu http://www.domain.com/en/about-usve sağlanan URL hem Google hem de İnsan ziyaretçiler için anlaşılması daha kolaydır. Bu seçeneği kullanarak varsayılan dillerle ne yaparız? Varsayılan dil, varsayılan olarak seçilen dili kaldırmalı mıdır? Yönlendirme Yani http://www.domain.com/en/about-usiçin http://www.domain.com/about-usCMS tek dil için kurulum olduğunda URL'de bu dil bir kimlik sahibi olmak gerek yoktur, çünkü benim gözünde ... Bu, en iyi çözümdür.

Ve üçüncü bir seçenek her iki seçenekten oluşan bir kombinasyondur: http://www.domain.com/about-usana dil için "dil-kimliksiz" -URL ( ) kullanılması. Ve alt diller için çevrilmiş bir SEO bilgisine sahip bir URL kullanın: http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

Umarım sorum başınızı çatlatır, kesin benimkini çatlattı! Burada bir soru olarak işlerimi halletmeme yardımcı oldu. Bana daha önce kullandığım yöntemleri ve yaklaşan CMS'im için sahip olduğum fikri gözden geçirme imkanı verdi.

Bu metni okumak için zaman ayırdığınız için şimdiden teşekkür ederim!

// Edit #1:

Bahsetmeyi unuttum: __ () işlevi belirli bir dizeyi çevirmek için bir takma addır. Bu yöntemde, henüz çeviri olmadığında varsayılan metnin yüklendiği bir çeşit geri dönüş yöntemi olmalıdır. Çeviri eksikse, ya eklenmeli ya da çeviri dosyası yeniden oluşturulmalıdır.


Yanıtlar:


115

Konu önermesi

Çok dilli bir sitede üç farklı özellik vardır:

  • arayüz çevirisi
  • içerik
  • url yönlendirme

Hepsi farklı şekillerde birbirine bağlı olsa da, CMS açısından farklı UI öğeleri kullanılarak yönetilir ve farklı şekilde saklanırlar. İlk ikisini uygulama ve anlayışınızdan emin görünüyorsunuz. Soru, ikinci yönüyle ilgiliydi - "URL Çevirisi? Bunu yapmalı mıyız yapmamalı mıyız? Ne şekilde?"

URL ne yapılabilir?

Çok önemli bir şey, IDN ile süslü olma . Bunun yerine harf çevirisi lehine (ayrıca: transkripsiyon ve romanizasyon). İlk bakışta IDN uluslararası URL'ler için uygun bir seçenek gibi görünse de, aslında iki nedenden ötürü reklamı yapılan gibi çalışmaz:

  • bazı tarayıcılar gibi ASCII olmayan karakterleri dönecek 'ч'ya 'ž'içine '%D1%87've'%C5%BE'
  • Kullanıcının özel temaları varsa, temanın yazı tipinde bu harfler için sembol bulunmayabilir

Aslında birkaç yıl önce Yii merkezli bir projede (korkunç çerçeve, IMHO) IDN yaklaşımını denedim. Bu çözümü kazımadan önce yukarıda belirtilen sorunların her ikisiyle de karşılaştım. Ayrıca, bunun bir saldırı vektörü olabileceğinden şüpheleniyorum.

Kullanılabilir seçenekler ... onları gördüğüm gibi.

Temel olarak iki seçeneğiniz var, bunlar şu şekilde soyutlanabilir:

  • http://site.tld/[:query]: burada [:query]hem dil hem de içerik seçimi belirlenir

  • http://site.tld/[:language]/[:query]: [:language]URL'nin bir kısmı dil seçimini tanımlar ve [:query]yalnızca içeriği tanımlamak için kullanılır

Sorgu Α ve Ω ..

Seçtiğinizi söyleyelim http://site.tld/[:query].

Bu durumda, birincil bir dil kaynağınız vardır: [:query]segmentin içeriği ; ve iki ek kaynak:

  • $_COOKIE['lang']söz konusu tarayıcı için değer
  • HTTP Accept-Language (1) , (2) başlığındaki dillerin listesi

İlk olarak, sorguyu tanımlı yönlendirme modellerinden biriyle eşleştirmeniz gerekir (seçiminiz Laravel ise, burada okuyun ). Desenin başarılı bir şekilde eşleşmesi üzerine dili bulmanız gerekir.

Desenin tüm bölümlerinden geçmek zorunda kalacaksınız. Tüm bu segmentler için potansiyel çevirileri bulun ve hangi dilin kullanıldığını belirleyin. İki ek kaynak (çerez ve başlık), ("if" değilse) ortaya çıktığında yönlendirme çakışmalarını çözmek için kullanılır.

Örneğin alın: http://site.tld/blog/novinka.

Bu harf çevirisi, "блог, новинка"İngilizce anlamı yaklaşık olarak "blog", "latest".

Zaten fark edebileceğiniz gibi, Rusça "блог" "blog" olarak tercüme edilecektir. Bu, ilk bölümünüz için [:query](en iyi durumda senaryoda ) ['en', 'ru']olası dillerin listesi ile sonuçlanacağı anlamına gelir . Sonra bir sonraki segment - "novinka" alırsınız. Yani olasılıklar listesinde yalnızca bir dil olabilir: ['ru'].

Listede bir öğe olduğunda, dili başarıyla buldunuz.

Ama eğer 2 (örnek: Rus ve Ukraynaca) veya daha fazla olasılıkla sonuçlanırsanız .. veya 0 olasılık, duruma göre olabilir. Doğru seçeneği bulmak için çerez ve / veya başlık kullanmanız gerekir.

Ve her şey başarısız olursa, sitenin varsayılan dilini seçersiniz.

Parametre olarak dil

Alternatif olarak, URL olarak tanımlanabilir http://site.tld/[:language]/[:query]. Bu durumda, sorguyu çevirirken, dili tahmin etmenize gerek yoktur, çünkü o noktada hangisini kullanacağınızı zaten biliyorsunuzdur.

Ayrıca ikincil bir dil kaynağı vardır: çerez değeri. Ancak burada Accept-Language üstbilgisi ile uğraşmanın bir anlamı yoktur, çünkü "soğuk başlatma" durumunda (kullanıcı ilk kez siteyi özel sorgu ile açtığında) bilinmeyen miktarda olası dil ile uğraşmazsınız.

Bunun yerine 3 basit, öncelikli seçeneğiniz vardır:

  1. Eğer [:language]kademeli ayarlanır kullanmak
  2. eğer $_COOKIE['lang']ayarlanırsa, onu kullanın
  3. varsayılan dili kullan

Dile sahip olduğunuzda, yalnızca sorguyu çevirmeye çalışırsınız ve çeviri başarısız olursa, söz konusu segment için (yönlendirme sonuçlarına dayalı olarak) "varsayılan değer" i kullanın.

Burada üçüncü bir seçenek yok mu?

Evet, teknik olarak iki yaklaşımı birleştirebilirsiniz, ancak bu süreci karmaşık hale ve sadece elle değişiklik URL'ye isteyen kişi karşılamak http://site.tld/en/newsiçin http://site.tld/de/newsve Almanca için değişime haber sayfası bekliyoruz.

Ancak bu durum bile daha az büyü ve umutla uygulamak için çerez değeri (önceki dil seçimi hakkında bilgi içerecek şekilde) kullanılarak azaltılabilir.

Hangi yaklaşım kullanılır?

Tahmin edebileceğiniz http://site.tld/[:language]/[:query]gibi, daha mantıklı bir seçenek olarak tavsiye ederim .

Ayrıca gerçek kelime durumunda, URL'de 3. başlık bulunur: "başlık". Online mağazada ürün adı veya haber sitesindeki makalenin başlığı gibi.

Misal: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency

Bu durumda '/news/article/121415'sorgu ve 'EU-as-global-reserve-currency'is başlığı olur. Tamamen SEO amaçlı.

Laravel'de yapılabilir mi?

Tür, ama varsayılan olarak değil.

Buna çok aşina değilim, ama gördüğüm kadarıyla Laravel basit desen tabanlı yönlendirme mekanizması kullanıyor. Çok dilli URL'leri uygulamak için büyük olasılıkla çekirdek sınıf (lar) ı genişletmeniz gerekecektir, çünkü çok dilli yönlendirme farklı depolama biçimlerine (veritabanı, önbellek ve / veya yapılandırma dosyaları) erişim gerektirir.

Yönlendirildi. Şimdi ne var?

Sonuç olarak iki değerli bilgi elde edersiniz: mevcut dil ve çevrilmiş sorgu bölümleri. Bu değerler daha sonra sonucu üretecek sınıf (lar) a gönderilmek için kullanılabilir.

Temel olarak, aşağıdaki URL: http://site.tld/ru/blog/novinka(veya olmayan sürüm '/ru') gibi bir şeye dönüşür

$parameters = [
   'language' => 'ru',
   'classname' => 'blog',
   'method' => 'latest',
];

Sadece sevkiyat için kullandığınız:

$instance = new {$parameter['classname']};
$instance->{'get'.$parameters['method']}( $parameters );

.. veya belirli bir uygulamaya bağlı olarak bazı varyasyonları.


1
Yeni bir fikir için teşekkürler! Çok düşünceli! URL parametresinde de dil parametresi olmasını düşünüyordum. Bu sadece belirli bir dili tanımlamak için en iyi yol gibi görünüyor, sadece kullanıcı için değil, SEO amaçları için de. Bir kullanıcının / en / news'i / de / news olarak değiştirmesi durumunda benim fikrim örneğin / de / nachrichten'a 301 (kalıcı) yönlendirme yapmaktı. Her dilin sayfa başına yalnızca bir benzersiz URL'si olduğundan emin olmak için (yine SEO amacıyla)
Joshua - Pendo 17:13

En iyi cevabı seçmek gittikçe zorlaşıyor, şu anda her birinin ödülünün en azından bir kısmını hak eden yaklaşık 3/4 cevap var. Birlikte, birlikte temizlemek istediğim her şeye sağlam bir cevap haline geliyor :)
Joshua - Pendo

URL çevirisinde verdiğiniz ayrıntılı yanıt için size en azından ekstra bir destek vermek üzere cevabınızı kabul ettim. Çok müteşekkir! Ancak, ödülümün her yönünü platformdan bağımsız bir şekilde cevapladığı için ödül, altınızdaki kişiye ödüllendirilir.
Joshua - Pendo

52

Thomas Bley tarafından önerilen bir Ön İşlemci Kullanarak Performans Vuruşu Olmadan i18n'i Uygulama

İş yerinde, son zamanlarda birkaç mülkümüzde i18n uygulamasını hayata geçirdik ve uğraştığımız şeylerden biri, anında çeviri ile uğraşmanın performans hitiydi, sonra Thomas Bley'nin bu harika blog gönderisini keşfettim bu da i18n'yi en düşük performans sorunlarıyla büyük trafik yüklerini işlemek için kullanma şeklimize ilham verdi.

PHP'de bildiğimiz gibi pahalı olan her çeviri işlemi için işlevleri çağırmak yerine, temel dosyalarımızı yer tutucularla tanımlıyoruz, ardından bu dosyaları önbelleğe almak için bir ön işlemci kullanıyoruz (sunduğumuzdan emin olmak için dosya değiştirme süresini saklıyoruz) her zaman en son içerik).

Çeviri Etiketleri

Thomas kullanımları {tr}ve {/tr}çevirileri başlangıç ve bitiş nerede etiketleri tanımlamak için. Nedeniyle biz dal kullanıyoruz olması nedeniyle, biz kullanmak istemiyoruz {Kullandığımız böylece karışıklığı önlemek için [%tr%]ve [%/tr%]bunun yerine. Temel olarak, bu şöyle görünür:

`return [%tr%]formatted_value[%/tr%];`

Thomas'ın dosyadaki temel İngilizce'yi önerdiğini unutmayın. Bunu yapmıyoruz çünkü değeri İngilizce olarak değiştirirsek tüm çeviri dosyalarını değiştirmek istemiyoruz.

INI Dosyaları

Ardından, her dil için şu biçimde bir INI dosyası oluştururuz placeholder = translated:

// lang/fr.ini
formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'

// lang/en_gb.ini
formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())

// lang/en_us.ini
formatted_value = '$' . number_format($value)

Bir kullanıcının bunları CMS içinde değiştirmesine izin vermek, sadece anahtar çiftlerini bir veya preg_spliton ile almak ve CMS'nin INI dosyalarına yazmasını sağlamak çok önemlidir.\n=

İşlemci Öncesi Bileşen

Aslında, Thomas çeviri dosyalarınızı almak ve diskte statik PHP dosyaları oluşturmak için tam zamanında bir 'derleyici' (gerçekte bu bir önişlemci) işlevini kullanmanızı önerir. Bu şekilde, dosyadaki her dize için bir çeviri işlevi çağırmak yerine çevrilmiş dosyalarımızı önbelleğe alırız:

// This function was written by Thomas Bley, not by me
function translate($file) {
  $cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
  // (re)build translation?
  if (!file_exists($cache_file)) {
    $lang_file = 'lang/'.LANG.'.ini';
    $lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';

    // convert .ini file into .php file
    if (!file_exists($lang_file_php)) {
      file_put_contents($lang_file_php, '<?php $strings='.
        var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
    }
    // translate .php into localized .php file
    $tr = function($match) use (&$lang_file_php) {
      static $strings = null;
      if ($strings===null) require($lang_file_php);
      return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
    };
    // replace all {t}abc{/t} by tr()
    file_put_contents($cache_file, preg_replace_callback(
      '/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
  }
  return $cache_file;
}

Not: Normal ifadenin çalıştığını doğrulamadım, şirket sunucumuzdan kopyalamadım, ancak işlemin nasıl çalıştığını görebilirsiniz.

Nasıl Aranır

Yine, bu örnek benden değil Thomas Bley'den:

// instead of
require("core/example.php");
echo (new example())->now();

// we write
define('LANG', 'en_us');
require(translate('core/example.php'));
echo (new example())->now();

Dili bir çerezde (veya çerez alamazsak oturum değişkeninde) saklarız ve ardından her istek üzerine alırız. $_GETDili geçersiz kılmak için bunu isteğe bağlı bir parametreyle birleştirebilirsiniz , ancak dil başına alt alan veya dil başına sayfa önermiyorum, çünkü hangi sayfaların popüler olduğunu ve gelen değerin azalmasını zorlaştıracaktır bağlantıları daha az yayılmış olacak.

Neden bu yöntemi kullanmalıyım?

Bu ön işleme yöntemini üç nedenden dolayı seviyoruz:

  1. Nadiren değişen içerikler için çok sayıda işlev çağırmamaktan büyük performans kazancı (bu sistemle, 100 bin Fransızca ziyaretçi, çeviri değiştirmeyi yalnızca bir kez çalıştırmaya devam edecektir).
  2. Basit düz dosyalar kullandığından ve saf bir PHP çözümü olduğundan veritabanımıza herhangi bir yük eklemez.
  3. Çevirilerimizde PHP ifadelerini kullanabilme.

Çevrilmiş Veritabanı İçeriğini Edinme

Veritabanımızdaki içerik için sadece bir sütun ekliyoruz language, daha sonra LANGdaha önce tanımladığımız sabit için bir erişimci yöntemi kullanıyoruz , böylece SQL çağrılarımız (ne yazık ki ZF1 kullanarak) şöyle görünüyor:

$query = select()->from($this->_name)
                 ->where('language = ?', User::getLang())
                 ->where('id       = ?', $articleId)
                 ->limit(1);

Makalelerimizde birincil anahtarın bir bileşimi vardır idve languagebu nedenle makale 54tüm dillerde bulunabilir. Bizim LANGvarsayılan en_USbelirtilmemiş ise.

URL Slug 'nun Çevirisi

Burada iki şeyi birleştirirdim, biri bootstrap'inizde $_GETdil için bir parametre kabul eden ve çerez değişkenini geçersiz kılan bir işlev ve diğeri birden fazla sümüklü böcek kabul eden yönlendirme. Ardından, yönlendirmenizde böyle bir şey yapabilirsiniz:

"/wilkommen" => "/welcome/lang/de"
... etc ...

Bunlar, yönetici panelinizden kolayca yazılabilecek düz bir dosyada saklanabilir. JSON veya XML bunları desteklemek için iyi bir yapı sağlayabilir.

Birkaç Diğer Seçenek Hakkında Notlar

PHP tabanlı Anında Çeviri

Bunların önceden işlenmiş çevirilere göre herhangi bir avantaj sunduğunu göremiyorum.

Ön Uç Tabanlı Çeviriler

Bunları uzun zamandır ilginç buldum, ancak birkaç uyarı var. Örneğin, web sitenizde çevirmeyi planladığınız tüm ifadelerin listesini kullanıcıya sunmanız gerekir; sitenin gizli tuttuğunuz veya erişmesine izin vermeyen alanlar varsa bu sorunlu olabilir.

Ayrıca, tüm kullanıcılarınızın sitenizde Javascript kullanmaya istekli olduğunu ve kullanabildiğini varsaymanız gerekir, ancak istatistiklerimden, kullanıcılarımızın yaklaşık% 2,5'i onsuz çalışıyor (veya sitelerimizin kullanmasını engellemek için Noscript kullanıyor) .

Veritabanına Dayalı Çeviriler

PHP'nin veritabanı bağlantı hızları ev hakkında yazmak için bir şey değildir ve bu, çevirmek için her ifadede bir fonksiyon çağırmanın zaten yüksek yüküne ekler. Performans ve ölçeklenebilirlik sorunları bu yaklaşımla ezici görünmektedir.


Sizi "Ön Uç Tercümeleri" ile karıştırdığımı görüyorum, demek istediğim ekranda çevrilen dizeleri ayrıştırmanın bir yoluydu. Kesinlikle istemci tarafında çevirmek için bir yol aramıyorum! Demek istediğim, ön uçtaki dilleri değiştirmenin en kolay yoluydu, ancak bu açıkça bir çerez veya kullanıcı ayarı kullanıyor :)
Joshua - Pendo 17:03

Oh, ve Veritabanına Dayalı olarak, tüm çevirileri yönetme yöntemine daha fazla odaklanıyordum, bu yüzden ideal çözümüm, bir veritabanına çeviriler ve ardından PHP'yi oluşturan ön işleme bileşenini üreten bir işlev yazan bir arka uç olurdu. dosya. Why?: basit .. Metindeki küçük değişikliklerden rahatsız olmak istemiyorum, kullanıcılar bir kod editörü ve / veya ftp programı kullanmadan kendileri yapabilmelidir :)
Joshua - Pendo

@PENDO Ön uç çevirileri demek istemediğinizi biliyorum, bu JS kullanarak ön uç çeviri çerçeveleri öneren kullanıcıya karşı ince örtülü bir yorumdu. ;)
Glitch Desire

@PENDO Kabul ediyorum, arka ucu önerdiğin gibi kullanacağım ama bir veritabanı yerine performans nedenleriyle düz bir dosya kullanacağım. Tabii ki, burada çekirdek öneri-pre render değiştirmek böylece değişimin ardından şablonları olan .INIile 3 sütun veritabanı tablosu dosyaları placeholder, replacement, language. Bileşik anahtar placeholderve language. Ardından tempfile(şablona giden yol) ve modified(DATETIME) ile başka bir 2-sütuna sahip olun .
Glitch Desire

1
@PENDO Teşekkürler. Ben 250 geri koydum ve her iki yanıtı doğru olarak seçtiğiniz için site bana izin verdiğinde 24 saat içinde teresko'ya vermeyi planlıyorum ve bence bir bölünme niyetlerinizi en iyi şekilde temsil edecektir.
Glitch Desire

15

Bir tekerlek icat etmemenizi ve gettext ve ISO dilleri kısaltma listesini kullanmamanızı öneririm. Popüler CMS'lerde veya çerçevelerde i18n / l10n'nin nasıl uygulandığını gördünüz mü?

Gettext'i kullanarak, birçok vakanın birden çok sayı biçimi gibi uygulandığı güçlü bir araca sahip olacaksınız. İngilizce'de sadece 2 seçeneğiniz var: tekil ve çoğul. Ancak Rusça'da örneğin 3 form vardır ve İngilizce'de olduğu kadar basit değildir.

Ayrıca birçok çevirmen gettext ile çalışma deneyimine sahiptir.

CakePHP veya Drupal'a bir göz atın . Her ikisi de çok dilli etkin. Arayüz yerelleştirmesi örneği olarak CakePHP ve içerik çevirisi örneği olarak Drupal.

L10n için veritabanı kullanmak hiç de geçerli değil. Sorgularda ton olacak. Standart yaklaşım, tüm l10n verilerini bellekte erken aşamada (ya da tembel yüklemeyi tercih ederseniz i10n işlevine ilk çağrı sırasında) almaktır. .Po dosyasından veya DB'den tüm verileri aynı anda okuyabilir. Ve sadece diziden istenen dizeleri okuyun.

Arayüzü çevirmek için çevrimiçi aracı uygulamanız gerekiyorsa, tüm bu verileri DB'de bulabilirsiniz, ancak yine de tüm verileri onunla çalışmak için dosyaya kaydedebilirsiniz. Bellekteki veri miktarını azaltmak için, çevrilmiş tüm mesajlarınızı / dizelerinizi gruplara bölebilirsiniz ve sadece mümkünse ihtiyacınız olan grupları yükleyebilirsiniz.

Yani # 3'te tamamen haklısın. Bir istisna dışında: genellikle bir denetleyici başına dosya veya benzeri olmayan büyük bir dosyadır. Çünkü performansın bir dosyayı açması en iyisidir. Bazı yüksek yüklü web uygulamalarının, dahil / gerektirdiğinde dosya işlemlerini önlemek için tüm PHP kodlarını bir dosyada derlediğini biliyorsunuzdur.

URL'ler hakkında. Google dolaylı olarak çeviriyi kullanmanızı önerir :

Fransızca içeriği açıkça belirtmek için: http://example.ca/fr/vélo-de-montagne.html

Ayrıca i varsayılan dil önek örneğin kullanıcıyı yönlendirmek gerektiğini düşünüyorum http://examlpe.com/about-us şuna yönlendirmeleri http://examlpe.com/en/about-us Ama eğer sitenizin kullanımı sadece bir dil size böylece hiç önek gerekmez.

Çıkış: http://www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 http: / /de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925

İçeriği çevirmek daha zordur. Makaleler, menü öğeleri vb. Gibi farklı içerik türleriyle bazı farklılıklar olacağını düşünüyorum. Ancak # 4'te doğru yoldasın. Daha fazla fikir edinmek için Drupal'a bir göz atın. Yeterince açık DB şeması ve çeviri için yeterince iyi bir arayüze sahiptir. Makaleyi oluşturup bunun için bir dil seçtiğiniz gibi. Ve daha sonra diğer dillere çevirebilirsiniz.

Drupal çeviri arayüzü

URL salyangozlarıyla ilgili bir sorun olmadığını düşünüyorum. Sadece sümüklü böcekler için ayrı bir tablo oluşturabilirsiniz ve doğru karar olacaktır. Ayrıca doğru indeksleri kullanarak, büyük miktarda veriyle bile tabloyu sorgulamak sorun değildir. Tam metin araması değildi, ancak slug için varchar veri türü kullanacaksa ve bu alanda da bir dizine sahipseniz dize eşleşmesi.

PS Üzgünüm, İngilizcem mükemmel olmaktan uzak.


Soruma cevap vermek için harcanan zaman için teşekkürler. İngilizcen anlayabileceğim kadar iyi! Çabalarınız için sizi + 1'leyeceğim!
Joshua - Pendo

Yaroslav, bir kez daha, cevabın için teşekkürler. Ancak ben biraz daha eksiksiz ve kodun arkasında kullanılan yöntemleri açıklamak yerine zaten orada olduğunu açıklamak 2 diğer cevaplar ile gitti .
Joshua - Pendo

2
Sorun değil. Gerçekten de bu cevaplar benim için okumak daha eksiksiz ve ilginç. Ama umarım cevabımdan da faydalı bir şey alırsınız.
Yaroslav

12

Web sitenizin içeriğine bağlıdır. İlk başta buradaki diğer tüm insanlar gibi bir veritabanı kullandım, ancak bir veritabanının tüm işlerini kodlamak zaman alabilir. Bunun ideal bir yöntem olduğunu söylemiyorum ve özellikle çok fazla metniniz varsa, ancak bir veritabanı kullanmadan hızlı bir şekilde yapmak istiyorsanız, bu yöntem işe yarayabilir, ancak kullanıcıların veri girmesine izin veremezsiniz çeviri dosyaları olarak kullanılacaktır. Ancak çevirileri kendiniz eklerseniz, işe yarayacaktır:

Diyelim ki şu metne sahipsiniz:

Welcome!

Bunu çeviriler içeren bir veritabanına girebilirsiniz, ancak bunu da yapabilirsiniz:

$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");

Şimdi, web siteniz bir çerez kullanıyorsa, buna örneğin:

$_COOKIE['language'];

Kolaylaştırmak için, kolayca kullanılabilecek bir koda dönüştürelim:

$language=$_COOKIE['language'];

Çerez diliniz Galce ise ve bu kod parçasına sahipseniz:

echo $welcome[$language];

Bunun sonucu:

Croeso!

Web siteniz için çok fazla çeviri eklemeniz gerekiyorsa ve bir veritabanı çok fazla tüketiyorsa, bir dizi kullanmak ideal bir çözüm olabilir.


1
Bu istediğim cevabın yakınında değil. Ayrıca, her sayfada tüm dilleri lang.en.phpkullanmak $lang['welcome']yerine, dahil edilecek ve her dosyada bildirilen dosyaları kullanmanız daha iyi olur.
Joshua - Pendo

7

Gerçekten gerçekten dağınık bir görev olabilir ve veri kodlama durumunda aşırı bir sorun olabilir çeviri için veritabanına bağımlı değil öneririz.

Daha önce benzer bir sorunla karşılaştım ve sorunumu çözmek için aşağıdaki sınıfı yazdım

Nesne: Yerel ayar \ Yerel ayar

<?php

  namespace Locale;

  class Locale{

// Following array stolen from Zend Framework
public $country_to_locale = array(
    'AD' => 'ca_AD',
    'AE' => 'ar_AE',
    'AF' => 'fa_AF',
    'AG' => 'en_AG',
    'AI' => 'en_AI',
    'AL' => 'sq_AL',
    'AM' => 'hy_AM',
    'AN' => 'pap_AN',
    'AO' => 'pt_AO',
    'AQ' => 'und_AQ',
    'AR' => 'es_AR',
    'AS' => 'sm_AS',
    'AT' => 'de_AT',
    'AU' => 'en_AU',
    'AW' => 'nl_AW',
    'AX' => 'sv_AX',
    'AZ' => 'az_Latn_AZ',
    'BA' => 'bs_BA',
    'BB' => 'en_BB',
    'BD' => 'bn_BD',
    'BE' => 'nl_BE',
    'BF' => 'mos_BF',
    'BG' => 'bg_BG',
    'BH' => 'ar_BH',
    'BI' => 'rn_BI',
    'BJ' => 'fr_BJ',
    'BL' => 'fr_BL',
    'BM' => 'en_BM',
    'BN' => 'ms_BN',
    'BO' => 'es_BO',
    'BR' => 'pt_BR',
    'BS' => 'en_BS',
    'BT' => 'dz_BT',
    'BV' => 'und_BV',
    'BW' => 'en_BW',
    'BY' => 'be_BY',
    'BZ' => 'en_BZ',
    'CA' => 'en_CA',
    'CC' => 'ms_CC',
    'CD' => 'sw_CD',
    'CF' => 'fr_CF',
    'CG' => 'fr_CG',
    'CH' => 'de_CH',
    'CI' => 'fr_CI',
    'CK' => 'en_CK',
    'CL' => 'es_CL',
    'CM' => 'fr_CM',
    'CN' => 'zh_Hans_CN',
    'CO' => 'es_CO',
    'CR' => 'es_CR',
    'CU' => 'es_CU',
    'CV' => 'kea_CV',
    'CX' => 'en_CX',
    'CY' => 'el_CY',
    'CZ' => 'cs_CZ',
    'DE' => 'de_DE',
    'DJ' => 'aa_DJ',
    'DK' => 'da_DK',
    'DM' => 'en_DM',
    'DO' => 'es_DO',
    'DZ' => 'ar_DZ',
    'EC' => 'es_EC',
    'EE' => 'et_EE',
    'EG' => 'ar_EG',
    'EH' => 'ar_EH',
    'ER' => 'ti_ER',
    'ES' => 'es_ES',
    'ET' => 'en_ET',
    'FI' => 'fi_FI',
    'FJ' => 'hi_FJ',
    'FK' => 'en_FK',
    'FM' => 'chk_FM',
    'FO' => 'fo_FO',
    'FR' => 'fr_FR',
    'GA' => 'fr_GA',
    'GB' => 'en_GB',
    'GD' => 'en_GD',
    'GE' => 'ka_GE',
    'GF' => 'fr_GF',
    'GG' => 'en_GG',
    'GH' => 'ak_GH',
    'GI' => 'en_GI',
    'GL' => 'iu_GL',
    'GM' => 'en_GM',
    'GN' => 'fr_GN',
    'GP' => 'fr_GP',
    'GQ' => 'fan_GQ',
    'GR' => 'el_GR',
    'GS' => 'und_GS',
    'GT' => 'es_GT',
    'GU' => 'en_GU',
    'GW' => 'pt_GW',
    'GY' => 'en_GY',
    'HK' => 'zh_Hant_HK',
    'HM' => 'und_HM',
    'HN' => 'es_HN',
    'HR' => 'hr_HR',
    'HT' => 'ht_HT',
    'HU' => 'hu_HU',
    'ID' => 'id_ID',
    'IE' => 'en_IE',
    'IL' => 'he_IL',
    'IM' => 'en_IM',
    'IN' => 'hi_IN',
    'IO' => 'und_IO',
    'IQ' => 'ar_IQ',
    'IR' => 'fa_IR',
    'IS' => 'is_IS',
    'IT' => 'it_IT',
    'JE' => 'en_JE',
    'JM' => 'en_JM',
    'JO' => 'ar_JO',
    'JP' => 'ja_JP',
    'KE' => 'en_KE',
    'KG' => 'ky_Cyrl_KG',
    'KH' => 'km_KH',
    'KI' => 'en_KI',
    'KM' => 'ar_KM',
    'KN' => 'en_KN',
    'KP' => 'ko_KP',
    'KR' => 'ko_KR',
    'KW' => 'ar_KW',
    'KY' => 'en_KY',
    'KZ' => 'ru_KZ',
    'LA' => 'lo_LA',
    'LB' => 'ar_LB',
    'LC' => 'en_LC',
    'LI' => 'de_LI',
    'LK' => 'si_LK',
    'LR' => 'en_LR',
    'LS' => 'st_LS',
    'LT' => 'lt_LT',
    'LU' => 'fr_LU',
    'LV' => 'lv_LV',
    'LY' => 'ar_LY',
    'MA' => 'ar_MA',
    'MC' => 'fr_MC',
    'MD' => 'ro_MD',
    'ME' => 'sr_Latn_ME',
    'MF' => 'fr_MF',
    'MG' => 'mg_MG',
    'MH' => 'mh_MH',
    'MK' => 'mk_MK',
    'ML' => 'bm_ML',
    'MM' => 'my_MM',
    'MN' => 'mn_Cyrl_MN',
    'MO' => 'zh_Hant_MO',
    'MP' => 'en_MP',
    'MQ' => 'fr_MQ',
    'MR' => 'ar_MR',
    'MS' => 'en_MS',
    'MT' => 'mt_MT',
    'MU' => 'mfe_MU',
    'MV' => 'dv_MV',
    'MW' => 'ny_MW',
    'MX' => 'es_MX',
    'MY' => 'ms_MY',
    'MZ' => 'pt_MZ',
    'NA' => 'kj_NA',
    'NC' => 'fr_NC',
    'NE' => 'ha_Latn_NE',
    'NF' => 'en_NF',
    'NG' => 'en_NG',
    'NI' => 'es_NI',
    'NL' => 'nl_NL',
    'NO' => 'nb_NO',
    'NP' => 'ne_NP',
    'NR' => 'en_NR',
    'NU' => 'niu_NU',
    'NZ' => 'en_NZ',
    'OM' => 'ar_OM',
    'PA' => 'es_PA',
    'PE' => 'es_PE',
    'PF' => 'fr_PF',
    'PG' => 'tpi_PG',
    'PH' => 'fil_PH',
    'PK' => 'ur_PK',
    'PL' => 'pl_PL',
    'PM' => 'fr_PM',
    'PN' => 'en_PN',
    'PR' => 'es_PR',
    'PS' => 'ar_PS',
    'PT' => 'pt_PT',
    'PW' => 'pau_PW',
    'PY' => 'gn_PY',
    'QA' => 'ar_QA',
    'RE' => 'fr_RE',
    'RO' => 'ro_RO',
    'RS' => 'sr_Cyrl_RS',
    'RU' => 'ru_RU',
    'RW' => 'rw_RW',
    'SA' => 'ar_SA',
    'SB' => 'en_SB',
    'SC' => 'crs_SC',
    'SD' => 'ar_SD',
    'SE' => 'sv_SE',
    'SG' => 'en_SG',
    'SH' => 'en_SH',
    'SI' => 'sl_SI',
    'SJ' => 'nb_SJ',
    'SK' => 'sk_SK',
    'SL' => 'kri_SL',
    'SM' => 'it_SM',
    'SN' => 'fr_SN',
    'SO' => 'sw_SO',
    'SR' => 'srn_SR',
    'ST' => 'pt_ST',
    'SV' => 'es_SV',
    'SY' => 'ar_SY',
    'SZ' => 'en_SZ',
    'TC' => 'en_TC',
    'TD' => 'fr_TD',
    'TF' => 'und_TF',
    'TG' => 'fr_TG',
    'TH' => 'th_TH',
    'TJ' => 'tg_Cyrl_TJ',
    'TK' => 'tkl_TK',
    'TL' => 'pt_TL',
    'TM' => 'tk_TM',
    'TN' => 'ar_TN',
    'TO' => 'to_TO',
    'TR' => 'tr_TR',
    'TT' => 'en_TT',
    'TV' => 'tvl_TV',
    'TW' => 'zh_Hant_TW',
    'TZ' => 'sw_TZ',
    'UA' => 'uk_UA',
    'UG' => 'sw_UG',
    'UM' => 'en_UM',
    'US' => 'en_US',
    'UY' => 'es_UY',
    'UZ' => 'uz_Cyrl_UZ',
    'VA' => 'it_VA',
    'VC' => 'en_VC',
    'VE' => 'es_VE',
    'VG' => 'en_VG',
    'VI' => 'en_VI',
    'VN' => 'vn_VN',
    'VU' => 'bi_VU',
    'WF' => 'wls_WF',
    'WS' => 'sm_WS',
    'YE' => 'ar_YE',
    'YT' => 'swb_YT',
    'ZA' => 'en_ZA',
    'ZM' => 'en_ZM',
    'ZW' => 'sn_ZW'
);

/**
 * Store the transaltion for specific languages
 *
 * @var array
 */
protected $translation = array();

/**
 * Current locale
 *
 * @var string
 */
protected $locale;

/**
 * Default locale
 *
 * @var string
 */
protected $default_locale;

/**
 *
 * @var string
 */
protected $locale_dir;

/**
 * Construct.
 *
 *
 * @param string $locale_dir            
 */
public function __construct($locale_dir)
{
    $this->locale_dir = $locale_dir;
}

/**
 * Set the user define localte
 *
 * @param string $locale            
 */
public function setLocale($locale = null)
{
    $this->locale = $locale;

    return $this;
}

/**
 * Get the user define locale
 *
 * @return string
 */
public function getLocale()
{
    return $this->locale;
}

/**
 * Get the Default locale
 *
 * @return string
 */
public function getDefaultLocale()
{
    return $this->default_locale;
}

/**
 * Set the default locale
 *
 * @param string $locale            
 */
public function setDefaultLocale($locale)
{
    $this->default_locale = $locale;

    return $this;
}

/**
 * Determine if transltion exist or translation key exist
 *
 * @param string $locale            
 * @param string $key            
 * @return boolean
 */
public function hasTranslation($locale, $key = null)
{
    if (null == $key && isset($this->translation[$locale])) {
        return true;
    } elseif (isset($this->translation[$locale][$key])) {
        return true;
    }

    return false;
}

/**
 * Get the transltion for required locale or transtion for key
 *
 * @param string $locale            
 * @param string $key            
 * @return array
 */
public function getTranslation($locale, $key = null)
{
    if (null == $key && $this->hasTranslation($locale)) {
        return $this->translation[$locale];
    } elseif ($this->hasTranslation($locale, $key)) {
        return $this->translation[$locale][$key];
    }

    return array();
}

/**
 * Set the transtion for required locale
 *
 * @param string $locale
 *            Language code
 * @param string $trans
 *            translations array
 */
public function setTranslation($locale, $trans = array())
{
    $this->translation[$locale] = $trans;
}

/**
 * Remove transltions for required locale
 *
 * @param string $locale            
 */
public function removeTranslation($locale = null)
{
    if (null === $locale) {
        unset($this->translation);
    } else {
        unset($this->translation[$locale]);
    }
}

/**
 * Initialize locale
 *
 * @param string $locale            
 */
public function init($locale = null, $default_locale = null)
{
    // check if previously set locale exist or not
    $this->init_locale();
    if ($this->locale != null) {
        return;
    }

    if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
        $this->detectLocale();
    } else {
        $this->locale = $locale;
    }

    $this->init_locale();
}

/**
 * Attempt to autodetect locale
 *
 * @return void
 */
private function detectLocale()
{
    $locale = false;

    // GeoIP
    if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

        $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

        if ($country) {

            $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
        }
    }

    // Try detecting locale from browser headers
    if (! $locale) {

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

            $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

            foreach ($languages as $lang) {

                $lang = str_replace('-', '_', trim($lang));

                if (strpos($lang, '_') === false) {

                    if (isset($this->country_to_locale[strtoupper($lang)])) {

                        $locale = $this->country_to_locale[strtoupper($lang)];
                    }
                } else {

                    $lang = explode('_', $lang);

                    if (count($lang) == 3) {
                        // language_Encoding_COUNTRY
                        $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                    } else {
                        // language_COUNTRY
                        $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                    }

                    return;
                }
            }
        }
    }

    // Resort to default locale specified in config file
    if (! $locale) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Check if config for selected locale exists
 *
 * @return void
 */
private function init_locale()
{
    if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Load a Transtion into array
 *
 * @return void
 */
private function loadTranslation($locale = null, $force = false)
{
    if ($locale == null)
        $locale = $this->locale;

    if (! $this->hasTranslation($locale)) {
        $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
    }
}

/**
 * Translate a key
 *
 * @param
 *            string Key to be translated
 * @param
 *            string optional arguments
 * @return string
 */
public function translate($key)
{
    $this->init();
    $this->loadTranslation($this->locale);

    if (! $this->hasTranslation($this->locale, $key)) {

        if ($this->locale !== $this->default_locale) {

            $this->loadTranslation($this->default_locale);

            if ($this->hasTranslation($this->default_locale, $key)) {

                $translation = $this->getTranslation($this->default_locale, $key);
            } else {
                // return key as it is or log error here
                return $key;
            }
        } else {
            return $key;
        }
    } else {
        $translation = $this->getTranslation($this->locale, $key);
    }
    // Replace arguments
    if (false !== strpos($translation, '{a:')) {
        $replace = array();
        $args = func_get_args();
        for ($i = 1, $max = count($args); $i < $max; $i ++) {
            $replace['{a:' . $i . '}'] = $args[$i];
        }
        // interpolate replacement values into the messsage then return
        return strtr($translation, $replace);
    }

    return $translation;
  }
}

kullanım

 <?php
    ## /locale/en.php

    return array(
       'name' => 'Hello {a:1}'
       'name_full' => 'Hello {a:1} {a:2}'
   );

$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');

echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');

Nasıl çalışır

{a:1}yerine yönteme iletilen 1. bağımsız değişken Locale::translate('key_name','arg1') {a:2}ile değiştirilir yönteme geçirilen 2. bağımsız değişken ile değiştirilirLocale::translate('key_name','arg1','arg2')

Algılama nasıl çalışır?

  • Varsayılan geoipolarak yüklüyse ülke kodunu tarafından döndürür geoip_country_code_by_nameve geoip yüklü değilse HTTP_ACCEPT_LANGUAGEbaşlığa geri dönüş

Bir veritabanı ne şekilde dağınık olurdu? Farklı dillerdeki olası karakterler nedeniyle? Şimdiye kadar çoğunlukla İngilizce, Fransızca, Hollandaca, Almanca web sitelerine sahibim, bu yüzden şimdilik sorun değil. Cevabınız için teşekkürler, ancak cevabın sadece bir parçası olduğu için ödül kazanmaz.
Joshua - Pendo

iyi soru sadece sizin için yararlı sanırım sadece gerekli dillere karşı Hintçe, Tayca, Çince ve Arapça (bu dil bir karakterleri temsil etmek için daha fazla 1 bayt alacak) kullanmayı düşünen bazı çocuklar olurdu. db kullanıyorsanız utf8_general_ci, harmanlama bunu yapmanın uygun yoludur.
Shushant

Katılıyorum, orada kendimi biraz iz var. İşaret ettiğiniz için teşekkürler, ayrıca çok bit karakterler bu soruda belirtilecek kadar önemlidir :)
Joshua - Pendo 18:13

5

Sadece bir alt cevap: Kesinlikle önünde bir dil tanımlayıcı ile çevrilmiş URL'leri kullanın: http://www.domain.com/nl/over-ons
Hibrit çözümler karmaşık olmaya eğilimlidir, bu yüzden sadece buna bağlı kalacağım. Neden? Çünkü URL SEO için gereklidir.

Db çevirisi hakkında: Dillerin sayısı az mı yoksa sabit mi? Yoksa tahmin edilemez ve dinamik mi? Düzeltilirse, sadece yeni sütunlar eklerdim, aksi takdirde birden çok tabloyla gider.

Ama genel olarak, neden Drupal kullanmıyorsunuz? Herkesin kendi CMS'lerini oluşturmak istediğini biliyorum çünkü daha hızlı, daha yalın, vs. vs. Ama bu gerçekten kötü bir fikir!


1
Cevabınız için teşekkürler. Drupal / Joomla'yı kullanmak istemememin nedeni basit: Sistemimin tüm giriş ve çıkışlarını, duraklanabilir kusurları, kodun nasıl oluşturulduğunu (ve önemli: birlikte 300 programcı tarafından oluşturulmadığını) emin olmak istiyorum. . Açık kaynak seçmemek için yeterli nedenim var. Bunun yanı sıra, şirketimin müşterilerim için önemli bir faktör olmasını istiyorum, başka bir geliştiriciye gidip beni hiçbir şey olmadan geride bırakmaları kötü bir şey.
Joshua - Pendo

7
Tüm bu nedenlerin tonlarca makalede tartışıldığını düşünüyorum. Müşterileriniz umarım sizi tam olarak seçmez, çünkü kimsenin sağlayamayacağı özel bir CMS'ye sahipsiniz. Ama her neyse, bu tamamen farklı bir tartışma.
Remy

1
Anladığınızı anlıyorum, yine de tüm giriş ve çıkışları bildiğim bir sistemi tercih ediyorum ve bir eklenti kullandığımda başkalarının çalıştığı işlere güvenmek için hiçbir şey hissetmiyorum.
Joshua - Pendo

1
Ayrıca, işimi yeterince iyi belgeleme eğilimindeyim, çünkü benim için çalışan “tek kişilik bir ordu” olduğum için sistemi tanımakta zorlanmamalılar.
Joshua - Pendo

Kötü fikir Drupal'ı seçmek ve hatta google, url'nin çevrilip çevrilmediğini umursamadıklarını söylüyor. Yine de bir yerel ayar kimliği içermelidir.
undefinedman

5

Zaten verilen cevapları düzeltmeye çalışmayacağım. Bunun yerine size kendi OOP PHP çerçevemin çevirileri nasıl ele aldığını anlatacağım.

Dahili olarak çerçevemde en, fr, es, cn vb. Kodlar kullanılır. Bir dizi, web sitesi tarafından desteklenen dilleri içerir: dizi ('en', 'fr', 'es', 'cn') Dil kodu $ _GET (lang = fr) üzerinden geçirilir ve geçilmezse veya geçerli değilse, dizideki ilk dile ayarlanmıştır. Böylece programın yürütülmesi sırasında ve en başından beri geçerli dil bilinir.

Tipik bir uygulamada çevrilmesi gereken içerik türünü anlamak yararlıdır:

1) sınıflardan hata mesajları (veya prosedür kodu) 2) sınıflardan hata mesajları (veya prosedür kodu) 3) sayfa içeriği (genellikle bir veritabanında saklanır) 4) site genelindeki dizeler (web sitesi adı gibi) 5) script- belirli dizeler

İlk türün anlaşılması kolaydır. Temel olarak, "veritabanına bağlanamadı ..." gibi mesajlardan bahsediyoruz. Bu iletilerin yalnızca bir hata oluştuğunda yüklenmesi gerekir. Yönetici sınıfım diğer sınıflardan bir çağrı alıyor ve parametreler olarak aktarılan bilgileri kullanarak sınıf klasörüne gidip hata dosyasını alıyor.

İkinci hata mesajı türü, bir formun doğrulanması yanlış olduğunda aldığınız mesajlara benzer. ("Boş bırakamazsınız" veya "lütfen 5 karakterden uzun bir şifre seçin"). Sınıf çalıştırmadan önce dizelerin yüklenmesi gerekir.

Asıl sayfa içeriği için her dil için bir kod kullanıyorum, her tablonun dili için kod öneki var. Yani en_content, İngilizce içerik barındıran tablo, es_content İspanya, Çin için cn_content ve fr_content Fransız şeyleridir.

Dize dördüncü tür web sitenizin her yerinde alakalı. Bu, dil kodu kullanılarak adlandırılan, en_lang.php, es_lang.php ve benzeri bir yapılandırma dosyası aracılığıyla yüklenir. Global dil dosyasında, dizi ('İngilizce', 'Çince', 'İspanyolca', 'Fransızca') gibi çevrilmiş dilleri İngilizce global dosya ve dizisine ('Anglais', 'Chinois', ' Espagnol ',' Francais '). Dolayısıyla, dil seçimi için bir açılır liste doldurduğunuzda, doğru dilde;)

Sonunda betiğe özgü dizeler var. Bir pişirme uygulaması yazarsanız, "Fırınınız yeterince sıcak değildi" olabilir.

Uygulama döngümde, önce genel dil dosyası yüklenir. Orada sadece küresel dizeleri ("Jack'in Web Sitesi" gibi) değil, aynı zamanda bazı sınıflar için ayarlar bulacaksınız. Temelde dile veya kültüre bağımlı olan her şey. Buradaki bazı dizelerde tarih maskeleri (MMDDYYYY veya DDMMYYYY) veya ISO Dil Kodları bulunur. Ana dil dosyasına, bireysel sınıflar için dizeler ekliyorum, çünkü bunlardan çok az var.

Diskten okunan ikinci ve son dil dosyası komut dosyası dil dosyasıdır. lang_en_home_welcome.php, home / welcome komut dosyasının dil dosyasıdır. Komut dosyası bir mod (ev) ve bir eylem (karşılama) ile tanımlanır. Her komut dosyasının config ve lang dosyalarına sahip kendi klasörü vardır.

Komut dosyası, içeriği yukarıda açıklandığı gibi içerik tablosunu adlandırarak veritabanından alır.

Bir şeyler ters giderse, yönetici dile bağlı hata dosyasının nereden alınacağını bilir. Bu dosya yalnızca bir hata durumunda yüklenir.

Dolayısıyla sonuç açıktır. Bir uygulama veya çerçeve geliştirmeye başlamadan önce çeviri sorunlarını düşünün. Ayrıca çevirileri içeren bir geliştirme iş akışına da ihtiyacınız vardır. Çerçevemle, tüm siteyi İngilizce olarak geliştiriyorum ve sonra ilgili tüm dosyaları çeviriyorum.

Çeviri dizelerinin uygulanma şeklindeki kısa bir son kelime. Çerçevemde başka bir hizmet için kullanılabilen hizmetleri çalıştıran tek bir global, $ yöneticisi var. Örneğin, form hizmeti html hizmetini alır ve html yazmak için kullanır. Sistemimdeki hizmetlerden biri çevirmen hizmetidir. $ translator-> set ($ service, $ code, $ string) geçerli dil için bir dize ayarlar. Dil dosyası bu tür ifadelerin bir listesidir. $ translator-> get ($ service, $ code) bir çeviri dizesi alır. $ Kodu 1 gibi sayısal veya 'no_connection' gibi bir dize olabilir. Her biri çevirmenin veri alanında kendi ad alanına sahip olduğu için hizmetler arasında çakışma olamaz.

Bunu, birkaç yıl önce yapmak zorunda olduğum gibi birisini yeniden icat etme görevini kurtaracağı umuduyla buraya gönderiyorum.


4

Symfony çerçevesini kullanmaya başlamadan önce bir süre önce aynı sorunu yaşadım .

  1. Arameters pageId (veya objectId, # 2'de açıklanan objectTable), hedef dil ve isteğe bağlı geri dönüş (varsayılan) dil parametresi olan bir __ () işlevini kullanmanız yeterlidir. Varsayılan dil, daha sonra değiştirmenin daha kolay bir yolunu bulmak için bazı global yapılandırmalarda ayarlanabilir.

  2. İçeriği veritabanında saklamak için aşağıdaki yapıyı kullandım: (pageId, dil, içerik, değişkenler).

    • pageId çevirmek istediğiniz sayfanıza bir FK olur. haberler, galeriler veya başka herhangi bir nesneniz varsa, onu 2 alana objectId, objectTable'a bölün.

    • dil - Açıkçası ISO dil dizesini EN_en, LT_lt, EN_us vb.

    • içerik - değişken değiştirme için joker karakterlerle birlikte çevirmek istediğiniz metin. Örnek "Merhaba mr. %% name %%. Hesap bakiyeniz %% balance %%."

    • değişkenler - json kodlu değişkenler. PHP bunları hızlı bir şekilde ayrıştırmak için işlevler sağlar. Örnek "isim: Laurynas, denge: 15.23".

    • ayrıca slug alanından bahsetmiştin. hızlı bir şekilde aramak için bu tabloya özgürce ekleyebilirsiniz.

  3. Çevirilerin önbelleğe alınmasıyla veritabanı çağrılarınız minimuma indirilmelidir. PHP dilinde en hızlı yapı olduğu için PHP dizisinde saklanmalıdır. Bu önbelleği nasıl yapacağınız size kalmış. Deneyimlerime göre, desteklenen her dil için bir klasör ve her pageId için bir dizi olması gerekir. Çeviriyi güncelledikten sonra önbellek yeniden oluşturulmalıdır. YALNIZCA değiştirilen dizi yeniden oluşturulmalıdır.

  4. sanırım # 2'de cevap verdim

  5. senin fikrin son derece mantıklı. Bu oldukça basit ve sanırım size herhangi bir sorun olmayacak.

URL'ler, çeviri tablosunda depolanan sülükler kullanılarak çevrilmelidir.

Son sözler

en iyi uygulamaları araştırmak her zaman iyidir, ancak tekerleği yeniden icat etmeyin. bileşenleri iyi bilinen çerçevelerden alıp kullanın ve kullanın.

Symfony çeviri bileşenine bir göz atın . Sizin için iyi bir kod tabanı olabilir.


Yorumunuz için teşekkürler, aldığınız zaman için +1 olarak bekleyin. Laravel (benim durumumda) yanılmıyorsam bazı Symfony parçaları kullanıyor, bu yüzden tekerleği yeniden icat etmemek konusunda kesinlikle haklısın. Bu soruyu (ve ödül) başkalarının çevirileri nasıl yaptığına dair bazı görüşler elde etmeye başladım, orada çok iyi uygulamalar olduğuna inanmaya başladım :-)
Joshua - Pendo

1

Kendime tekrar tekrar ilgili sorular sordum, sonra resmi dillerde kayboldum ... ama sadece biraz yardım etmek için bazı bulguları paylaşmak istiyorum:

Gelişmiş CMS'ye göz atmanızı öneririm

Typo3için PHP (Ben orada bir sürü şey ama en olgun olduğunu düşünüyorum birini şu biliyorum)

Plone içinde Python

2013'te web'in farklı çalışması gerektiğini öğrenirseniz, sıfırdan başlayın. Bu, yeni bir CMS oluşturmak için yüksek vasıflı / deneyimli kişilerden oluşan bir ekip oluşturmak anlamına gelir. Belki de bu amaç için polimere bir göz atmak istersiniz.

Kodlama ve çok dilli web siteleri / ana dil desteği söz konusu olduğunda, her programcının unicode hakkında bir ipucu olması gerektiğini düşünüyorum. Unicode bilmiyorsanız kesinlikle verilerinizi bozarsınız. Binlerce ISO koduyla gitmeyin. Size sadece biraz hafıza kazandıracaklar. Ama UTF-8 ile kelimenin tam anlamıyla her şeyi yapabilirsiniz, hatta Çin karakterlerini saklar. Ancak bunun için temelde bir utf-16 veya utf-32 yapan 2 veya 4 baytlık karakterleri depolamanız gerekir.

URL kodlaması ile ilgili ise, yine orada kodlamaları karıştırmamalısınız ve en azından alan adı için, tarayıcı gibi uygulamalar sağlayan farklı lobiler tarafından tanımlanan kurallar olduğunu unutmayın. Örneğin, bir Alan aşağıdakine çok benzer olabilir:

ьankofamerica.com veya bankofamerica.com samesamebutdifferent;)

Tabii ki tüm kodlamalarla çalışmak için dosya sistemine ihtiyacınız var. Utf-8 dosya sistemini kullanarak unicode için başka bir artı.

Çevirilerle ilgili ise, belgelerin yapısı hakkında düşünün. örneğin bir kitap veya makale. Bu docbookyapılar hakkında anlaşılacak şartlara sahipsiniz . Ama HTML'de sadece içerik blokları hakkında. Dolayısıyla, bu düzeyde, ayrıca web sayfası düzeyinde veya alan adı düzeyinde bir çeviri elde etmek istersiniz. Dolayısıyla, bir blok mevcut değilse, orada değil, bir web sayfası yoksa üst gezinme düzeyine yönlendirilirsiniz. Bir alanın navigasyon yapısında tamamen farklı olması gerekiyorsa, .. yönetmek için tamamen farklı bir yapı. Bu zaten Typo3 ile yapılabilir.

Eğer çerçeveleri hakkında, bildiğim en olgun olanlar, MVC gibi genel şeyler yapmak için (buzzword gerçekten nefret ediyorum! "Performans" gibi Bir şey satmak istiyorsanız, performans ve featurerich kelimesini kullanın ve satıyorsunuz ... ne cehennem) Zend. Php kaos kodlayıcılara standartlar getirmek için iyi bir şey olduğu kanıtlanmıştır. Ancak, typo3'ün CMS dışında bir de çerçevesi vardır. Son zamanlarda yeniden geliştirildi ve şimdi flow3 olarak adlandırılıyor. Tabii ki çerçeveler, veritabanı soyutlama, şablonlama ve önbellekleme kavramlarını kapsar, ancak bireysel güçlü yönlere sahiptir.

Önbellekleme ile ilgili ise ... bu çok karmaşık / çok katmanlı olabilir. PHP'de hızlandırıcı, opcode, aynı zamanda html, httpd, mysql, xml, css, js ... her türlü önbellek hakkında düşüneceksiniz. Tabii ki bazı bölümler önbelleğe alınmalı ve blog yanıtları gibi dinamik bölümler olmamalıdır. Bazıları üretilen URL'lerle AJAX üzerinden talep edilmelidir. JSON, hashbang vb.

Ardından, web sitenizdeki küçük bileşenlerin yalnızca belirli kullanıcılar tarafından erişilmesini veya yönetilmesini istersiniz , bu nedenle kavramsal olarak büyük bir rol oynar.

Ayrıca yapmak istiyorum istatistik veritabanları arasında farklı tip ihtiyacı böylece, belki dağıttınız sistemi / Facebooks vb herhangi bir yazılım facebook ... senin yüzünden üst düzey cms üzerine inşa edilecek , bigdata, xml BELLEK, herhangi .

sanırım şimdilik yeterli. Yazım hatası / plone veya bahsi geçen çerçeveler duymadıysanız, çalışacak kadar var. Bu yolda henüz sormadığınız sorular için birçok çözüm bulacaksınız.

Eğer düşünürseniz, yeni bir CMS yapalım çünkü 2013 ve php yine de ölmek üzere, o zaman başka bir geliştirici grubuna katılmayı umuyoruz.

İyi şanslar!

Ve btw. insanların gelecekte herhangi bir web sitesi olmayacak olmasına ne dersiniz? ve hepimiz google + 'da olacağız? Umarım geliştiriciler biraz daha yaratıcı olurlar ve faydalı bir şeyler yaparlar (borgle tarafından asimile edilmemeleri için)

//// Düzenle /// Mevcut uygulamanız için biraz düşünülmüş:

Eğer bir php mysql CMS varsa ve multilang desteği gömmek istedim. tablonuzu herhangi bir dil için bir ad sütunuyla kullanabilir veya çeviriyi aynı tabloya bir nesne tanıtıcısı ve bir dil tanıtıcısı ile ekleyebilir veya herhangi bir dil için özdeş bir tablo oluşturabilir ve oraya nesne ekleyebilir, ardından bir seçim birliği yapabilirsiniz görüntülemeleri için. Veritabanı için utf8 genel ci kullanın ve elbette ön / arka uçta utf8 metin / kodlama kullanın. URL'ler için zaten açıkladığınız şekilde URL yolu segmentlerini kullandım

domain.org/tr/about, lang kimliğini içerik tablonuzla eşleyebilirsiniz. yine de URL'leriniz için bir parametreler haritanız olması gerekir, böylece URL'nizdeki bir yol bölümünden eşlenecek bir parametre tanımlamak istersiniz.

domain.org/en/about/employees/IT/administrators/

arama yapılandırması

pageid | uRL

1 | /about/employees/../ ..

1 | /../about/employees../../

parametreleri url yol bölümüyle eşleştir ""

$parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
$parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
$parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result 

$websiteconfig[]=$userwhatever;
$websiteconfig[]=$parameterlist;
$someparameterlist[] = array("branch"=>$someid);
$someparameterlist[] = array("employertype"=>$someid);
function getURL($someparameterlist){ 
// todo foreach someparameter lookup pathsegment 
return path;
}

söylemek gerekirse, bu zaten üst yazı kaplı.

Ve unutmamak için, url'yi çoğu durumda index.php olan php dosyanıza yeniden yazmanız gerekir.


Yorum için teşekkürler, kesinlikle düşünmem gereken şeyler var. Ben zaten birkaç yıldır utf8 kodlama kullanıyordum, bir zamanlar karakterle mücadele ederdim ;-) Diğer tarafta, aradığım gibi CMS / Framework türünün cevabınızda hiçbir faktör olmaması gerekiyordu sıfırdan kodlama yapıyormuşuz gibi platformdan bağımsız bir yöntem.
Joshua - Pendo

Eğer gerçekten sıfırdan kodlamak istiyorsanız Dartlang ve polimere bir göz atmanızı tavsiye ederim. Dartlang tarayıcıda çalışıyor ve 32 ve 64 bit desteğine sahip olduğundan ve sunucu tarafında çoğu amaç için kullanılabildiğinden ve dart2js derleyicisine sahip olduğu için gerçekten incelenmeye değer. İnsanlar platform bağımsızlığından bahsediyorlarsa java hakkında düşünüyorlar ... bunun ne anlama geldiğini biliyoruz. Yapım süreci ... Sanırım değişim için JSON kullanırdım. hashbangs ve serverside ile oluşturulan web sitesi clientide .. iyi işbirliği sağlamak için ne isterseniz yapın.
Dr.Dama

Veritabanı düzeni ve nesil mantığı ana görevdir. Nobodys bunu sizin için burada yapacak ... ama önemli olan şey Fikrin kendisi. Lobileri umursamadım ama işleri halletmek için umarım modeller yaratabilir ve bazı şeyler paylaşabilirsiniz. Şu anda benzer görevler üzerinde çalışıyorum. Ama hala planlıyorum. Typo3'ü arka uç olarak görüyorum ve yeni bir istemci yapısı oluşturuyorum. Çok Dilli kalıp arka uçta çözülür ve bilgileri arama motorları / web hizmetleri için özel bir şekilde paylaşır. Her neyse, tüm bağlamsal ve sürekli bir bina görevi
Dr. Dama

-1

Veritabanı çalışması:

Dil Tablosu 'diller' oluşturun:

Alanlar:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

'İçerik' veritabanında bir tablo oluşturun:

Alanlar:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

Ön Uç Çalışması:

Kullanıcı açılır menüden veya herhangi bir alandan herhangi bir dil seçtiğinde, seçilen dil kimliğini aşağıdaki gibi oturumda kaydedin,

$_SESSION['language']=1;

Şimdi, oturumda depolanan dil kimliğine göre veritabanı tablosundaki 'içerik'ten veri alın.

Ayrıntıları burada bulabilirsiniz http://skillrow.com/multilingual-website-in-php-2/


1
Bu, daha sonra ihtiyaç duyulan basit dil entegrasyonunun bir yoludur, tüm yazıları okumaya ve cevapları vermeye çalıştınız mı?
Joshua - Pendo

-2

Hemen hemen tüm sitenin Fransızca ve İngilizce olduğu Quebec'te yaşayan bir kişi olarak ... WP için çok dilli değilse birçok denemeliyim ... tüm sitemle nive çalışan tek kullanışlı bir çözüm mQtranslate ... onunla yaşıyorum ve ölüyorum!

https://wordpress.org/plugins/mqtranslate/


1
evet, WP sorunun hiçbir faktörü değildi. Bu bir yorum olabilirdi aswel
Joshua - Pendo

-3

Ne hakkında WORDPRESS + MULTI-LANGUAGE SITE BASIS(eklentisi)? sitenin yapısı olacaktır:

  • example.com/ eng / category1 / ....
  • example.com/ eng / sayfam ....
  • example.com/ rus / kategori1 / ....
  • example.com/ rus / sayfam ....

Eklenti, Çeviri için tüm ifadeleri basit bir mantıkla Arayüz sağlar:

(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"

o zaman çıktı alınabilir:
echo translate('my_title', LNG); // LNG is auto-detected

ps, ancak eklentinin hala etkin olup olmadığını kontrol edin.


3
ve ispanyolca "Holla userio" değil "Hola Usuario"
bheatcoker

1
Lol Holla userio, komikti!
spekdrum

İspanyolca (sadece kullanılan örnek) bilmiyordu nedeni için acele acele !! :)
T.Todua

-5

Javascript yükleyebileceğiniz herhangi bir web sitesi ile çalışan gerçekten basit bir seçenek www.multilingualizer.com

Tüm diller için tüm metni tek bir sayfaya koymanıza ve ardından kullanıcının görmesi gerekmeyen dilleri gizlemenize olanak tanır. İyi çalışıyor.


Dikkat, SEO çok kötü olurdu! Ayrıca, sadece kötü bir uygulama olan bir kısmına ihtiyacınız varken tüm içeriği yüklersiniz.
Hafenkranich

Site sadece İngilizce ... garip şeyler neden çözümlerini kullanmıyorum ??
eduardo.lopes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.