Situs web multi bahasa praktik terbaik


179

Saya telah bergumul dengan pertanyaan ini selama beberapa bulan sekarang, tetapi saya belum pernah berada dalam situasi yang saya perlukan untuk mengeksplorasi semua opsi yang mungkin sebelumnya. Saat ini, saya merasa sudah waktunya untuk mengetahui kemungkinan dan membuat preferensi pribadi saya untuk digunakan dalam proyek mendatang saya.

Biarkan saya membuat sketsa situasi yang saya cari

Saya akan memutakhirkan / mengembangkan kembali sistem manajemen konten yang sudah lama saya gunakan. Namun, saya merasa multi bahasa merupakan perbaikan besar untuk sistem ini. Sebelumnya saya tidak menggunakan kerangka kerja apa pun tetapi saya akan menggunakan Laraval4 untuk proyek yang akan datang. Laravel tampaknya merupakan pilihan terbaik dari cara yang lebih bersih untuk kode PHP. Sidenote: Laraval4 should be no factor in your answer. Saya mencari cara umum terjemahan yang platform / kerangka kerja independen.

Apa yang harus diterjemahkan

Karena sistem yang saya cari harus semudah mungkin digunakan untuk pengguna, metode untuk mengelola terjemahan harus berada dalam CMS. Seharusnya tidak perlu memulai koneksi FTP untuk memodifikasi file terjemahan atau templat yang diuraikan html / php.

Selain itu, saya mencari cara termudah untuk menerjemahkan beberapa tabel basis data mungkin tanpa perlu membuat tabel tambahan.

Apa yang saya hasilkan dengan diri saya sendiri

Karena saya sudah mencari, membaca, dan mencoba berbagai hal sendiri. Ada beberapa opsi yang saya miliki. Tetapi saya masih merasa belum mencapai metode praktik terbaik untuk apa yang sebenarnya saya cari. Saat ini, inilah yang saya buat, tetapi metode ini juga memiliki efek sampingnya.

  1. PHP Parsed Templates : sistem template harus diuraikan oleh PHP. Dengan cara ini saya dapat memasukkan parameter yang diterjemahkan ke dalam HTML tanpa harus membuka template dan memodifikasinya. Selain itu, templat PHP yang diurai memberi saya kemampuan untuk memiliki 1 templat untuk situs web lengkap alih-alih memiliki subfolder untuk setiap bahasa (yang pernah saya miliki sebelumnya). Metode untuk mencapai target ini bisa berupa Smarty, TemplatePower, Laravel's Blade atau parser templat lainnya. Seperti yang saya katakan ini harus independen terhadap solusi tertulis.
  2. Database Driven : mungkin saya tidak perlu menyebutkan ini lagi. Tetapi solusinya harus didorong oleh basis data. CMS ditujukan untuk berorientasi objek dan MVC, jadi saya perlu memikirkan struktur data logis untuk string. Seperti template saya akan terstruktur: template / Controller / view.php mungkin struktur ini akan membuat paling masuk akal: Controller.View.parameter. Tabel database akan memiliki bidang ini panjang dengan valuebidang. Di dalam templat kita bisa menggunakan semacam metode sortir echo __('Controller.View.welcome', array('name', 'Joshua'))dan parameternya berisi Welcome, :name. Demikian hasilnya Welcome, Joshua. Ini sepertinya cara yang baik untuk melakukan ini, karena parameter seperti: nama mudah dimengerti oleh editor.
  3. Beban Basis Data Rendah : Tentu saja sistem di atas akan menyebabkan banyak beban basis data jika string ini sedang dimuat saat bepergian. Oleh karena itu saya memerlukan sistem caching yang merender ulang file bahasa segera setelah diedit / disimpan di lingkungan administrasi. Karena file dihasilkan, tata letak sistem file yang baik juga diperlukan. Saya kira kita bisa pergi dengan languages/en_EN/Controller/View.phpatau .ini, apa pun yang paling cocok untuk Anda. Mungkin .ini bahkan diurai lebih cepat pada akhirnya. Cetakan ini harus berisi data dalam format parameter=value; . Saya kira ini adalah cara terbaik untuk melakukan ini, karena setiap Tampilan yang diberikan dapat menyertakan file bahasa sendiri jika ada. Parameter bahasa kemudian harus dimuat ke tampilan tertentu dan tidak dalam lingkup global untuk mencegah parameter saling menimpa.
  4. Terjemahan Tabel Database : ini sebenarnya adalah hal yang paling saya khawatirkan. Saya sedang mencari cara untuk membuat terjemahan Berita / Halaman / etc. secepat mungkin. Memiliki dua tabel untuk setiap modul (misalnya Newsdan News_translations) adalah suatu pilihan tetapi rasanya banyak pekerjaan untuk mendapatkan sistem yang baik. Salah satu hal yang saya buat berdasarkan pada data versioningsistem yang saya tulis: ada satu nama tabel database Translations, tabel ini memiliki kombinasi unik language, tablenamedanprimarykey. Misalnya: en_En / Berita / 1 (Mengacu pada versi Bahasa Inggris dari item Berita dengan ID = 1). Tetapi ada 2 kerugian besar untuk metode ini: pertama-tama tabel ini cenderung menjadi cukup panjang dengan banyak data dalam database dan yang kedua adalah pekerjaan yang berat untuk menggunakan pengaturan ini untuk mencari tabel. Misalnya mencari seong SEO dari item akan menjadi pencarian teks lengkap, yang cukup bodoh. Tetapi di sisi lain: ini adalah cara cepat untuk membuat konten yang dapat diterjemahkan di setiap tabel dengan sangat cepat, tapi saya tidak percaya pro ini melebihi con.
  5. Pekerjaan Front-end : Front-end juga membutuhkan pemikiran. Tentu saja kami akan menyimpan bahasa yang tersedia dalam database dan (de) aktifkan yang kami butuhkan. Dengan cara ini skrip dapat menghasilkan dropdown untuk memilih bahasa dan back-end dapat memutuskan secara otomatis terjemahan apa yang dapat dibuat menggunakan CMS. Bahasa yang dipilih (misalnya en_EN) kemudian akan digunakan ketika mendapatkan file bahasa untuk tampilan atau untuk mendapatkan terjemahan yang tepat untuk item konten di situs web.

Jadi, itulah mereka. Ide saya sejauh ini. Mereka bahkan tidak menyertakan opsi pelokalan untuk tanggal dll, tetapi karena server saya mendukung PHP5.3.2 + pilihan terbaik adalah dengan menggunakan ekstensi intl seperti yang dijelaskan di sini: http://devzone.zend.com/1500/internationalization-in -php-53 / - tapi ini akan berguna di stadium pengembangan selanjutnya. Untuk saat ini masalah utamanya adalah bagaimana mendapatkan praktik terbaik penerjemahan konten di situs web.

Selain semua yang saya jelaskan di sini, saya masih memiliki hal lain yang belum saya putuskan, sepertinya pertanyaan sederhana, tetapi sebenarnya itu membuat saya sakit kepala:

Terjemahan URL? Haruskah kita melakukan ini atau tidak? dan dengan cara apa?

Jadi .. jika saya memiliki url ini: http://www.domain.com/about-usdan bahasa Inggris adalah bahasa default saya. Haruskah URL ini diterjemahkan http://www.domain.com/over-onsketika saya memilih bahasa Belanda sebagai bahasa saya? Atau haruskah kita menempuh jalan yang mudah dan cukup mengubah konten halaman yang terlihat di /about. Hal terakhir sepertinya bukan pilihan yang valid karena itu akan menghasilkan beberapa versi dari URL yang sama, pengindeksan konten ini akan gagal dengan cara yang benar.

Pilihan lain adalah menggunakan http://www.domain.com/nl/about-us. Ini menghasilkan setidaknya URL unik untuk setiap konten. Juga ini akan lebih mudah untuk pergi ke bahasa lain, misalnya http://www.domain.com/en/about-usdan URL yang disediakan lebih mudah dipahami untuk Google dan pengunjung Manusia. Dengan menggunakan opsi ini, apa yang kita lakukan dengan bahasa default? Haruskah bahasa default menghapus bahasa yang dipilih secara default? Jadi mengalihkan http://www.domain.com/en/about-uske http://www.domain.com/about-us... Di mata saya ini adalah solusi terbaik, karena ketika CMS diatur hanya untuk satu bahasa tidak perlu memiliki identifikasi bahasa ini di URL.

Dan opsi ketiga adalah kombinasi dari kedua opsi: menggunakan "bahasa-identifikasi-kurang" -URL ( http://www.domain.com/about-us) untuk bahasa utama. Dan gunakan URL dengan slug SEO yang diterjemahkan untuk bahasa lain: http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

Saya harap pertanyaan saya membuat kepala Anda retak, mereka retak saya pasti! Memang sudah membantu saya untuk menyelesaikan masalah sebagai pertanyaan di sini. Memberi saya kemungkinan untuk meninjau metode yang saya gunakan sebelumnya dan ide yang saya miliki untuk CMS mendatang.

Saya ingin mengucapkan terima kasih sudah meluangkan waktu untuk membaca teks ini!

// Edit #1:

Saya lupa menyebutkan: fungsi __ () adalah alias untuk menerjemahkan string yang diberikan. Dalam metode ini jelas harus ada semacam metode mundur di mana teks default dimuat ketika belum ada terjemahan yang tersedia. Jika terjemahannya hilang maka harus dimasukkan atau file terjemahan harus dibuat ulang.


Jawaban:


115

Premis topik

Ada tiga aspek berbeda dalam situs multibahasa:

  • terjemahan antarmuka
  • kandungan
  • perutean url

Sementara mereka semua saling berhubungan dengan cara yang berbeda, dari sudut pandang CMS mereka dikelola menggunakan elemen UI yang berbeda dan disimpan secara berbeda. Anda tampaknya percaya diri dalam implementasi dan pemahaman Anda tentang dua yang pertama. Pertanyaannya adalah tentang aspek yang terakhir - "Penerjemahan URL? Haruskah kita melakukan ini atau tidak? Dan dengan cara apa?"

Dari mana URL bisa dibuat?

Hal yang sangat penting adalah, jangan membiasakan diri dengan IDN . Alih-alih mendukung transliterasi (juga: transkripsi dan romanisasi). Meskipun sekilas IDN tampaknya menjadi opsi yang layak untuk URL internasional, sebenarnya IDN tidak berfungsi seperti yang diiklankan karena dua alasan:

  • beberapa browser akan mengubah karakter non-ASCII seperti 'ч'atau 'ž'menjadi '%D1%87'dan'%C5%BE'
  • jika pengguna memiliki tema khusus, font tema tersebut kemungkinan besar tidak memiliki simbol untuk huruf-huruf itu

Saya benar-benar mencoba pendekatan IDN beberapa tahun yang lalu dalam proyek berbasis Yii (framework mengerikan, IMHO). Saya menemui kedua masalah yang disebutkan di atas sebelum mengikis solusi itu. Juga, saya menduga itu mungkin vektor serangan.

Opsi yang tersedia ... seperti yang saya lihat.

Pada dasarnya Anda memiliki dua pilihan, yang dapat disarikan sebagai:

  • http://site.tld/[:query]: di mana [:query]menentukan pilihan bahasa dan konten

  • http://site.tld/[:language]/[:query]: di mana [:language]bagian dari URL menentukan pilihan bahasa dan [:query]hanya digunakan untuk mengidentifikasi konten

Kueri adalah Α dan Ω ..

Katakanlah Anda memilih http://site.tld/[:query].

Dalam hal ini Anda memiliki satu sumber bahasa utama: konten [:query]segmen; dan dua sumber tambahan:

  • nilai $_COOKIE['lang']untuk browser tertentu
  • daftar bahasa dalam tajuk Bahasa Terima HTTP (1) , (2)

Pertama, Anda harus mencocokkan kueri dengan salah satu pola perutean yang ditentukan (jika pilihan Anda adalah Laravel, lalu baca di sini ). Pada pencocokan pola yang berhasil, Anda perlu menemukan bahasa.

Anda harus melewati semua segmen pola. Temukan terjemahan potensial untuk semua segmen tersebut dan tentukan bahasa mana yang digunakan. Dua sumber tambahan (cookie dan header) akan digunakan untuk menyelesaikan konflik routing, ketika (bukan "jika") mereka muncul.

Ambil contoh: http://site.tld/blog/novinka.

Itu transliterasi "блог, новинка", yang dalam bahasa Inggris berarti sekitar "blog", "latest".

Seperti yang sudah Anda perhatikan, dalam bahasa Rusia "блог" akan ditransliterasikan sebagai "blog". Yang berarti bahwa untuk bagian pertama dari [:query]Anda (dalam skenario kasus terbaik ) akan berakhir dengan ['en', 'ru']daftar bahasa yang mungkin. Kemudian Anda mengambil segmen berikutnya - "novinka". Yang mungkin hanya memiliki satu bahasa pada daftar kemungkinan: ['ru'].

Ketika daftar memiliki satu item, Anda telah berhasil menemukan bahasa.

Tetapi jika Anda berakhir dengan 2 (contoh: Rusia dan Ukraina) atau lebih banyak kemungkinan .. atau 0 kemungkinan, sebagai kasus mungkin. Anda harus menggunakan cookie dan / atau header untuk menemukan opsi yang benar.

Dan jika semuanya gagal, Anda memilih bahasa default situs.

Bahasa sebagai parameter

Alternatifnya adalah menggunakan URL, yang dapat didefinisikan sebagai http://site.tld/[:language]/[:query]. Dalam hal ini, ketika menerjemahkan kueri, Anda tidak perlu menebak bahasa, karena pada saat itu Anda sudah tahu mana yang harus digunakan.

Ada juga sumber bahasa kedua: nilai cookie. Tetapi di sini tidak ada gunanya mengacaukan tajuk Bahasa Terima, karena Anda tidak berurusan dengan jumlah bahasa yang tidak diketahui dalam kasus "cold start" (ketika pengguna pertama kali membuka situs dengan permintaan khusus).

Alih-alih, Anda memiliki 3 opsi sederhana yang diprioritaskan:

  1. jika [:language]segmen diatur, gunakan itu
  2. jika $_COOKIE['lang']diatur, gunakan itu
  3. gunakan bahasa default

Ketika Anda memiliki bahasa, Anda hanya mencoba menerjemahkan kueri, dan jika terjemahan gagal, gunakan "nilai default" untuk segmen tertentu (berdasarkan hasil routing).

Bukankah ini opsi ketiga?

Ya, secara teknis Anda dapat menggabungkan kedua pendekatan, tapi itu akan mempersulit proses dan hanya menampung orang-orang yang ingin URL perubahan secara manual dari http://site.tld/en/newsuntuk http://site.tld/de/newsdan mengharapkan halaman berita perubahan ke Jerman.

Tetapi bahkan kasus ini kemungkinan dapat dikurangi dengan menggunakan nilai cookie (yang akan berisi informasi tentang pilihan bahasa sebelumnya), untuk diterapkan dengan sedikit sihir dan harapan.

Pendekatan mana yang digunakan?

Seperti yang mungkin sudah Anda duga, saya akan merekomendasikan http://site.tld/[:language]/[:query]sebagai opsi yang lebih masuk akal.

Juga dalam situasi kata sebenarnya Anda akan memiliki bagian utama ke-3 di URL: "title". Seperti dalam nama produk di toko online atau judul artikel di situs berita.

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

Dalam hal ini '/news/article/121415'akan menjadi permintaan, dan 'EU-as-global-reserve-currency'judul is. Murni untuk keperluan SEO.

Bisakah itu dilakukan di Laravel?

Agak, tapi tidak secara default.

Saya tidak terlalu terbiasa dengan itu, tetapi dari apa yang saya lihat, Laravel menggunakan mekanisme routing berbasis pola sederhana. Untuk menerapkan URL multibahasa, Anda mungkin harus memperluas kelas inti , karena perutean multibahasa membutuhkan akses ke berbagai bentuk penyimpanan (database, cache dan / atau file konfigurasi).

Itu dialihkan. Apa sekarang?

Sebagai hasilnya, Anda akan mendapatkan dua informasi berharga: bahasa saat ini dan segmen kueri yang diterjemahkan. Nilai-nilai ini kemudian dapat digunakan untuk mengirim ke kelas yang akan menghasilkan hasilnya.

Pada dasarnya, URL berikut: http://site.tld/ru/blog/novinka(atau versi tanpa '/ru') diubah menjadi sesuatu seperti

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

Yang hanya Anda gunakan untuk pengiriman:

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

.. atau beberapa variasi dari itu, tergantung pada implementasi tertentu.


1
Terima kasih atas wawasan lainnya! Sangat bijaksana! Saya berpikir untuk memiliki parameter bahasa di URL juga. Ini sepertinya cara terbaik untuk mengidentifikasi bahasa tertentu, tidak hanya untuk pengguna tetapi juga untuk keperluan SEO. Dalam hal pengguna mengubah / id / berita ke / de / news, ide saya adalah melakukan 301 (permanen) redirect ke / de / nachrichten misalnya. Hanya untuk memastikan setiap bahasa hanya memiliki satu URL unik per halaman (lagi untuk keperluan SEO)
Joshua - Pendo

Semakin sulit untuk memilih jawaban terbaik, saat ini ada sekitar 3/4 jawaban yang pantas setidaknya menjadi bagian dari hadiah masing-masing. Gabungan mereka menjadi jawaban yang solid untuk semua hal yang ingin saya jelaskan bersama-sama :)
Joshua - Pendo

Saya menerima jawaban Anda untuk memberi Anda setidaknya beberapa perwakilan ekstra untuk jawaban terperinci yang Anda berikan pada terjemahan URL. Sangat dihargai! Namun, hadiah itu adalah penghargaan bagi orang di bawah Anda karena dia menjawab setiap aspek pertanyaan saya dengan cara yang independen.
Joshua - Pendo

52

Menerapkan i18n Tanpa Kinerja Hit Menggunakan Pre-Processor seperti yang disarankan oleh Thomas Bley

Di tempat kerja, kami baru-baru ini menerapkan i18n pada beberapa properti kami, dan salah satu hal yang terus kami perjuangkan adalah kinerja yang hebat dalam menangani terjemahan on-the-fly, kemudian saya menemukan posting blog yang hebat ini oleh Thomas Bley yang menginspirasi cara kami menggunakan i18n untuk menangani beban lalu lintas besar dengan masalah kinerja minimal.

Alih-alih memanggil fungsi untuk setiap operasi terjemahan, yang seperti yang kita tahu di PHP mahal, kami mendefinisikan file dasar kami dengan placeholder, kemudian menggunakan pra-prosesor untuk menyimpan file-file tersebut (kami menyimpan waktu modifikasi file untuk memastikan kami melayani konten terbaru setiap saat).

Tag Terjemahan

Thomas menggunakan {tr}dan {/tr}menandai untuk menentukan dari mana awal dan akhir terjemahan. Karena kami menggunakan TWIG, kami tidak ingin menggunakan {untuk menghindari kebingungan sehingga kami menggunakan [%tr%]dan [%/tr%]sebaliknya. Pada dasarnya, ini terlihat seperti ini:

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

Perhatikan bahwa Thomas menyarankan menggunakan bahasa Inggris dasar dalam file. Kami tidak melakukan ini karena kami tidak ingin harus memodifikasi semua file terjemahan jika kami mengubah nilainya dalam bahasa Inggris.

File INI

Kemudian, kami membuat file INI untuk setiap bahasa, dalam format 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)

Itu akan sepele untuk memungkinkan pengguna untuk memodifikasi ini di dalam CMS, hanya mendapatkan keypairs dengan preg_splitpada \natau =dan membuat CMS mampu menulis ke file INI.

Komponen Pra-Prosesor

Pada dasarnya, Thomas menyarankan menggunakan 'kompiler' tepat waktu (meskipun, sebenarnya, ini adalah preprosesor) berfungsi seperti ini untuk mengambil file terjemahan Anda dan membuat file PHP statis pada disk. Dengan cara ini, kami pada dasarnya menyimpan file yang diterjemahkan alih-alih memanggil fungsi terjemahan untuk setiap string dalam file:

// 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;
}

Catatan: Saya tidak memverifikasi bahwa regex berfungsi, saya tidak menyalinnya dari server perusahaan kami, tetapi Anda dapat melihat bagaimana operasi bekerja.

Cara Menyebutnya

Sekali lagi, contoh ini dari Thomas Bley, bukan dari saya:

// 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();

Kami menyimpan bahasa dalam cookie (atau variabel sesi jika kami tidak bisa mendapatkan cookie) dan kemudian mengambilnya pada setiap permintaan. Anda dapat menggabungkan ini dengan $_GETparameter opsional untuk mengganti bahasa, tetapi saya tidak menyarankan subdomain-per-bahasa atau halaman-per-bahasa karena itu akan mempersulit untuk melihat halaman mana yang populer dan akan mengurangi nilai inbound tautan karena Anda akan lebih jarang menyebar.

Mengapa menggunakan metode ini?

Kami menyukai metode preprocessing ini karena tiga alasan:

  1. Keuntungan kinerja luar biasa dari tidak memanggil sejumlah besar fungsi untuk konten yang jarang berubah (dengan sistem ini, 100 ribu pengunjung di Prancis masih akan hanya menjalankan penggantian terjemahan sekali saja).
  2. Itu tidak menambah beban ke database kami, karena menggunakan flat-file sederhana dan merupakan solusi PHP murni.
  3. Kemampuan untuk menggunakan ekspresi PHP dalam terjemahan kami.

Mendapatkan Konten Database yang Diterjemahkan

Kami hanya menambahkan kolom untuk konten dalam basis data kami yang disebut language, kemudian kami menggunakan metode accessor untuk LANGkonstanta yang kami definisikan sebelumnya, sehingga panggilan SQL kami (menggunakan ZF1, sayangnya) terlihat seperti ini:

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

Artikel kami memiliki kunci primer senyawa lebih iddan languagejadi artikel 54bisa eksis dalam semua bahasa. LANGDefault kami adalah en_USjika tidak ditentukan.

Terjemahan Slug URL

Saya akan menggabungkan dua hal di sini, satu adalah fungsi di bootstrap Anda yang menerima $_GETparameter untuk bahasa dan mengabaikan variabel cookie, dan yang lainnya adalah routing yang menerima banyak siput. Maka Anda dapat melakukan sesuatu seperti ini di perutean:

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

Ini dapat disimpan dalam file datar yang dapat dengan mudah ditulis dari panel admin Anda. JSON atau XML dapat memberikan struktur yang baik untuk mendukung mereka.

Catatan Mengenai Beberapa Pilihan Lain

Penerjemahan On-The-Fly berbasis PHP

Saya tidak dapat melihat bahwa ini menawarkan keunggulan dibandingkan terjemahan yang sudah diproses.

Terjemahan Berbasis Front-end

Saya sudah lama menemukan ini menarik, tetapi ada beberapa peringatan. Misalnya, Anda harus menyediakan bagi pengguna seluruh daftar frasa di situs web yang ingin Anda terjemahkan, ini bisa bermasalah jika ada area situs yang Anda sembunyikan atau tidak mengizinkan mereka mengaksesnya.

Anda juga harus berasumsi bahwa semua pengguna Anda bersedia dan dapat menggunakan Javascript di situs Anda, tetapi dari statistik saya, sekitar 2,5% dari pengguna kami berjalan tanpa itu (atau menggunakan Noscript untuk memblokir situs kami agar tidak menggunakannya) .

Terjemahan Berbasis Database

Kecepatan konektivitas basis data PHP bukan apa-apa untuk dituliskan di rumah, dan ini menambah biaya panggilan yang sudah tinggi pada setiap frase untuk diterjemahkan. Masalah kinerja & skalabilitas tampak luar biasa dengan pendekatan ini.


Saya melihat bahwa saya telah membingungkan Anda dengan "Front-end Translations", yang saya maksud adalah cara untuk mengurai string yang diterjemahkan di layar. Saya jelas tidak mencari cara untuk menerjemahkannya di sisi klien! Apa yang saya maksud adalah cara termudah untuk beralih bahasa di front-end, tapi itu jelas menggunakan cookie atau pengaturan pengguna :)
Joshua - Pendo

Oh, dan oleh Database-Driven saya lebih mengarah pada metode mengelola semua terjemahan, jadi solusi ideal saya adalah back-end yang menulis terjemahan ke database diikuti oleh fungsi yang menghasilkan komponen pra-pemrosesan yang menghasilkan PHP mengajukan. Why?: simple .. Saya ingin tidak ingin repot dengan sedikit perubahan dalam teks, pengguna harus dapat melakukannya sendiri tanpa menggunakan editor kode dan / atau program ftp :)
Joshua - Pendo

@PENDO Saya tahu Anda tidak bermaksud terjemahan front-end, itu adalah komentar terselubung terhadap pengguna yang menyarankan kerangka kerja terjemahan front-end menggunakan JS. ;)
Glitch Desire

@ PENDO Saya setuju, saya akan menggunakan backend seperti yang Anda sarankan tetapi alih-alih database saya akan menggunakan file flat untuk alasan kinerja. Tentu saja, inti saran di sini adalah pra-render template pada perubahan sehingga Anda bisa mengganti .INIfile dengan tabel database 3-kolom dengan placeholder, replacement, language. Tombol majemuk aktif placeholderdan language. Kemudian miliki 2-col lain dengan tempfile(path to templat) dan modified(DATETIME).
Glitch Desire

1
@PENDO Terima kasih. Saya telah memasang 250 cadangan dan saya berencana untuk memberikannya kepada teresko dalam 24 jam ketika situs memungkinkan saya, karena Anda memilih kedua jawaban sebagai benar, dan saya pikir perpecahan akan mewakili niat Anda.
Glitch Desire

15

Saya sarankan Anda untuk tidak menciptakan roda dan menggunakan daftar singkatan bahasa gettext dan ISO. Pernahkah Anda melihat bagaimana i18n / l10n diimplementasikan dalam CMS atau kerangka kerja populer?

Menggunakan gettext Anda akan memiliki alat yang ampuh di mana banyak kasus sudah diimplementasikan seperti bentuk angka jamak. Dalam bahasa Inggris Anda hanya memiliki 2 pilihan: tunggal dan jamak. Tetapi dalam bahasa Rusia misalnya ada 3 bentuk dan tidak sesederhana dalam bahasa Inggris.

Juga banyak penerjemah yang sudah memiliki pengalaman untuk bekerja dengan gettext.

Lihatlah CakePHP atau Drupal . Keduanya multibahasa diaktifkan. CakePHP sebagai contoh pelokalan antarmuka dan Drupal sebagai contoh terjemahan konten.

Untuk l10n menggunakan basis data sama sekali tidak terjadi. Ini akan menjadi ton pada permintaan. Pendekatan standar adalah untuk mendapatkan semua data l10n dalam memori pada tahap awal (atau selama panggilan pertama ke fungsi i10n jika Anda lebih suka malas memuat). Itu bisa membaca dari file .po atau dari DB semua data sekaligus. Dan daripada hanya membaca string yang diminta dari array.

Jika Anda perlu mengimplementasikan alat online untuk menerjemahkan antarmuka, Anda dapat memiliki semua data dalam DB tetapi daripada menyimpan semua data ke file untuk bekerja dengannya. Untuk mengurangi jumlah data dalam memori, Anda dapat membagi semua pesan / string yang diterjemahkan ke dalam grup dan hanya memuat grup yang Anda perlukan jika memungkinkan.

Jadi Anda benar-benar tepat di # 3 Anda. Dengan satu pengecualian: biasanya itu adalah satu file besar bukan file per-controller atau lebih. Karena kinerja terbaik untuk membuka satu file. Anda mungkin tahu bahwa beberapa aplikasi web dengan beban tinggi mengkompilasi semua kode PHP dalam satu file untuk menghindari operasi file ketika menyertakan / mengharuskan dipanggil.

Tentang URL. Google secara tidak langsung menyarankan untuk menggunakan terjemahan:

untuk dengan jelas menunjukkan konten bahasa Prancis: http://example.ca/fr/vélo-de-montagne.html

Juga saya pikir Anda perlu mengarahkan pengguna ke awalan bahasa default mis. Http://examlpe.com/about-us akan dialihkan ke http://examlpe.com/en/about-us Tetapi jika situs Anda hanya menggunakan satu bahasa, maka Anda tidak perlu awalan sama sekali.

Lihat: 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

Menerjemahkan konten adalah tugas yang lebih sulit. Saya pikir ini akan menjadi beberapa perbedaan dengan berbagai jenis konten misalnya artikel, item menu dll. Tapi di # 4 Anda berada di jalan yang benar. Lihatlah Drupal untuk memiliki lebih banyak ide. Ini memiliki skema DB yang cukup jelas dan antarmuka yang cukup baik untuk menerjemahkan. Seperti Anda membuat artikel dan memilih bahasa untuknya. Dan kemudian Anda bisa menerjemahkannya ke bahasa lain.

Antarmuka terjemahan Drupal

Saya pikir itu tidak masalah dengan siput URL. Anda bisa membuat tabel terpisah untuk siput dan itu akan menjadi keputusan yang tepat. Juga menggunakan indeks yang tepat itu tidak masalah untuk query tabel bahkan dengan sejumlah besar data. Dan itu bukan pencarian teks lengkap tetapi pencocokan string jika akan menggunakan tipe data varchar untuk slug dan Anda dapat memiliki indeks pada bidang itu juga.

PS Maaf, bahasa Inggris saya jauh dari sempurna.


Terima kasih telah meluangkan waktu untuk menjawab pertanyaan saya. Bahasa Inggris Anda cukup baik untuk saya mengerti! Saya akan memberi Anda +1 untuk upaya Anda!
Joshua - Pendo

Yaroslav, sekali lagi, terima kasih atas jawaban Anda. Namun saya pergi dengan 2 jawaban lain yang mana sedikit lebih lengkap dan menjelaskan metode yang digunakan di balik kode daripada menunjukkan itu sudah ada.
Joshua - Pendo

2
Tidak masalah. Memang jawaban itu lebih lengkap dan menarik untuk dibaca untuk saya juga. Tapi saya harap Anda mendapatkan sesuatu yang bermanfaat dari jawaban saya juga.
Yaroslav

12

Itu tergantung pada seberapa banyak konten yang dimiliki situs web Anda. Pada awalnya saya menggunakan database seperti semua orang lain di sini, tetapi bisa memakan waktu untuk membuat skrip semua cara kerja dari database. Saya tidak mengatakan bahwa ini adalah metode yang ideal dan terutama jika Anda memiliki banyak teks, tetapi jika Anda ingin melakukannya dengan cepat tanpa menggunakan database, metode ini bisa berfungsi, Anda tidak dapat mengizinkan pengguna untuk memasukkan data yang akan digunakan sebagai file terjemahan. Tetapi jika Anda menambahkan terjemahan sendiri, itu akan berhasil:

Katakanlah Anda memiliki teks ini:

Welcome!

Anda dapat memasukkan ini dalam database dengan terjemahan, tetapi Anda juga dapat melakukan ini:

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

Sekarang, jika situs web Anda menggunakan cookie, Anda memiliki ini sebagai contoh:

$_COOKIE['language'];

Untuk membuatnya lebih mudah, mari kita transformasikan dalam kode yang dapat dengan mudah digunakan:

$language=$_COOKIE['language'];

Jika bahasa cookie Anda adalah bahasa Wales dan Anda memiliki kode ini:

echo $welcome[$language];

Hasilnya adalah:

Croeso!

Jika Anda perlu menambahkan banyak terjemahan untuk situs web Anda dan database terlalu banyak, menggunakan array bisa menjadi solusi yang ideal.


1
Ini bukan-mana dekat jawaban yang saya minta. Selain itu, daripada memiliki semua bahasa tersedia di setiap halaman, Anda sebaiknya membuat file seperti lang.en.phpyang disertakan dan menggunakan $lang['welcome']yang dinyatakan dalam setiap file.
Joshua - Pendo

7

Saya akan menyarankan Anda untuk tidak benar-benar bergantung pada database untuk terjemahan, itu bisa benar-benar tugas yang berantakan dan bisa menjadi masalah ekstrim dalam hal pengkodean data.

Saya telah menghadapi masalah yang sama beberapa waktu lalu dan menulis kelas berikut untuk menyelesaikan masalah saya

Objek: Lokal \ Lokal

<?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;
  }
}

Pemakaian

 <?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');

Bagaimana itu bekerja

{a:1}diganti dengan argumen 1 yang diteruskan ke metode Locale::translate('key_name','arg1') {a:2}diganti dengan argumen 2 yang diteruskan ke metodeLocale::translate('key_name','arg1','arg2')

Cara kerja deteksi

  • Secara default jika geoipdiinstal maka akan mengembalikan kode negara oleh geoip_country_code_by_namedan jika geoip tidak diinstal fallback ke HTTP_ACCEPT_LANGUAGEheader

Dengan cara apa database menjadi berantakan? Karena kemungkinan karakter dalam berbagai bahasa? Sejauh ini saya terutama memiliki situs web Inggris, Prancis, Belanda, Jerman sehingga tidak ada masalah untuk saat ini. Terima kasih atas jawabannya, tetapi karena itu hanya sebagian dari jawaban itu tidak akan memenangkan hadiah.
Joshua - Pendo

baik saya kira pertanyaan Anda hanya membantu Anda hanya akan ada beberapa orang yang akan mempertimbangkan menggunakan bahasa seperti Hindi, Thailand, Cina dan Arab (bahasa ini akan membutuhkan lebih dari 1 byte untuk mewakili karakter) terhadap bahasa yang Anda butuhkan. jika Anda menggunakan db maka utf8_general_cicollation adalah cara yang tepat untuk melakukannya.
Shushant

Saya setuju, saya punya sedikit jejak di sana. Terima kasih telah menunjukkan, karakter multi bit juga cukup penting untuk disebutkan dalam pertanyaan ini :)
Joshua - Pendo

5

Hanya sebuah sub jawaban: Benar-benar menggunakan url yang diterjemahkan dengan pengenal bahasa di depannya: http://www.domain.com/nl/over-ons
Solusi Hybride cenderung rumit, jadi saya akan tetap menggunakannya. Mengapa? Karena url sangat penting untuk SEO.

Tentang terjemahan db: Apakah jumlah bahasa lebih atau kurang tetap? Atau lebih tepatnya tidak terduga dan dinamis? Jika sudah diperbaiki, saya hanya akan menambahkan kolom baru, jika tidak pergi dengan beberapa tabel.

Tetapi secara umum, mengapa tidak menggunakan Drupal? Saya tahu semua orang ingin membangun CMS mereka sendiri karena lebih cepat, lebih ramping, dll. Tapi itu hanya ide yang buruk!


1
Terima kasih atas jawaban anda. Alasan saya tidak ingin menggunakan Drupal / Joomla adalah sederhana: Saya ingin memastikan bahwa saya tahu semua seluk beluk sistem saya, kekurangan yang dapat ditanggulangi, bagaimana kode dibuat (dan penting: tidak dibangun oleh 300 programmer bersama-sama) . Saya punya lebih dari cukup alasan untuk tidak memilih open source. Selain itu, saya ingin perusahaan saya menjadi faktor penting bagi klien saya, itu hal buruk bahwa mereka dapat pergi ke pengembang lain dan meninggalkan saya tanpa apa-apa.
Joshua - Pendo

7
Saya pikir semua alasan ini diperdebatkan dalam banyak artikel. Pelanggan Anda diharapkan tidak akan memilih Anda secara tepat karena Anda memiliki CMS eksklusif yang tidak dapat dipertahankan orang lain. Tapi bagaimanapun, itu adalah diskusi yang sangat berbeda.
Remy

1
Saya mengerti maksud Anda, masih saya lebih suka sistem yang saya tahu semua seluk beluk dan saya tidak merasa apa-apa untuk mengandalkan siapa pun yang bekerja ketika saya menggunakan plugin.
Joshua - Pendo

1
Selain itu, saya cenderung mendokumentasikan pekerjaan saya dengan cukup baik, karena saya adalah orang "tentara satu orang" yang bekerja untuk saya seharusnya tidak kesulitan mengenal sistem.
Joshua - Pendo

Gagasan yang buruk adalah memilih Drupal dan bahkan google mengatakan bahwa mereka tidak peduli apakah url diterjemahkan atau tidak. Itu harus mengandung pengidentifikasi lokal.
undefinedman

5

Saya tidak akan berusaha untuk memperbaiki jawaban yang sudah diberikan. Alih-alih, saya akan memberi tahu Anda tentang cara kerangka kerja OOP PHP saya sendiri menangani terjemahan.

Secara internal, kerangka kerja saya menggunakan kode seperti en, fr, es, cn dan sebagainya. Array menampung bahasa yang didukung oleh situs web: array ('en', 'fr', 'es', 'cn') Kode bahasa dilewatkan melalui $ _GET (lang = fr) dan jika tidak lulus atau tidak valid, itu diatur ke bahasa pertama dalam array. Jadi kapan saja selama eksekusi program dan dari awal, bahasa saat ini dikenal.

Sangat berguna untuk memahami jenis konten yang perlu diterjemahkan dalam aplikasi yang khas:

1) pesan kesalahan dari kelas (atau kode prosedural) 2) pesan non-kesalahan dari kelas (atau kode prosedural) 3) konten halaman (biasanya disimpan dalam database) 4) string seluruh situs (seperti nama situs web) 5) skrip- string spesifik

Tipe pertama mudah dimengerti. Pada dasarnya, kita berbicara tentang pesan seperti "tidak dapat terhubung ke database ...". Pesan-pesan ini hanya perlu dimuat ketika kesalahan terjadi. Kelas manajer saya menerima panggilan dari kelas lain dan menggunakan informasi yang diteruskan sebagai parameter hanya pergi ke folder kelas yang relevan dan mengambil file kesalahan.

Jenis pesan kesalahan kedua lebih seperti pesan yang Anda dapatkan ketika validasi formulir salah. ("Anda tidak dapat meninggalkan ... kosong" atau "silakan pilih kata sandi dengan lebih dari 5 karakter"). String perlu dimuat sebelum kelas dijalankan. Saya tahu apa itu

Untuk konten halaman yang sebenarnya, saya menggunakan satu tabel per bahasa, setiap tabel diawali oleh kode untuk bahasa tersebut. Jadi en_content adalah tabel dengan konten bahasa Inggris, es_content adalah untuk spanyol, cn_content untuk China dan fr_content adalah barang Prancis.

Jenis string keempat relevan di seluruh situs web Anda. Ini dimuat melalui file konfigurasi bernama menggunakan kode untuk bahasa, yaitu en_lang.php, es_lang.php dan sebagainya. Dalam file bahasa global Anda perlu memuat bahasa yang diterjemahkan seperti array ('Inggris', 'Cina', 'Spanyol', 'Prancis') dalam file global dan array ('Anglais', 'Chinois', ' Espagnol ',' Francais ') dalam file Prancis. Jadi ketika Anda mengisi dropdown untuk pemilihan bahasa, itu dalam bahasa yang benar;)

Akhirnya Anda memiliki string khusus skrip. Jadi, jika Anda menulis aplikasi memasak, mungkin "oven Anda tidak cukup panas".

Dalam siklus aplikasi saya, file bahasa global dimuat terlebih dahulu. Di sana Anda akan menemukan tidak hanya string global (seperti "Situs Web Jack") tetapi juga pengaturan untuk beberapa kelas. Pada dasarnya segala sesuatu yang bergantung pada bahasa atau budaya. Beberapa string di sana termasuk topeng untuk tanggal (MMDDYYYY atau DDMMYYYY), atau Kode Bahasa ISO. Dalam file bahasa utama, saya menyertakan string untuk kelas-kelas individual karena jumlahnya sangat sedikit.

File bahasa kedua dan terakhir yang dibaca dari disk adalah file bahasa skrip. lang_en_home_welcome.php adalah file bahasa untuk skrip home / welcome. Script didefinisikan oleh mode (rumah) dan tindakan (selamat datang). Setiap skrip memiliki folder sendiri dengan file config dan lang.

Script menarik konten dari database yang menamai tabel konten seperti yang dijelaskan di atas.

Jika terjadi kesalahan, manajer tahu di mana mendapatkan file kesalahan yang tergantung pada bahasa. File itu hanya dimuat jika ada kesalahan.

Jadi kesimpulannya jelas. Pikirkan masalah terjemahan sebelum Anda mulai mengembangkan aplikasi atau kerangka kerja. Anda juga membutuhkan alur kerja pengembangan yang menggabungkan terjemahan. Dengan kerangka kerja saya, saya mengembangkan seluruh situs dalam bahasa Inggris dan kemudian menerjemahkan semua file yang relevan.

Hanya kata terakhir yang cepat tentang cara penerapan string terjemahan. Kerangka kerja saya memiliki satu global, manajer $, yang menjalankan layanan yang tersedia untuk layanan lain. Jadi misalnya layanan formulir mendapatkan layanan html dan menggunakannya untuk menulis html. Salah satu layanan di sistem saya adalah layanan penerjemah. $ translator-> set ($ service, $ code, $ string) menetapkan string untuk bahasa saat ini. File bahasa adalah daftar pernyataan seperti itu. $ translator-> get ($ service, $ code) mengambil string terjemahan. $ Code dapat berupa angka seperti 1 atau string seperti 'no_connection'. Tidak ada bentrokan antara layanan karena masing-masing memiliki namespace sendiri di area data penerjemah.

Saya memposting ini di sini dengan harapan ini akan menyelamatkan seseorang dari tugas menciptakan kembali roda seperti yang harus saya lakukan beberapa tahun yang lalu.


4

Saya memiliki masalah yang sama beberapa waktu lalu, sebelum mulai menggunakan kerangka kerja Symfony .

  1. Cukup gunakan fungsi __ () yang memiliki arameters pageId (atau objectId, objectTable dijelaskan dalam # 2), bahasa target dan parameter opsional bahasa fallback (default). Bahasa default dapat diatur dalam beberapa konfigurasi global untuk memiliki cara yang lebih mudah untuk mengubahnya nanti.

  2. Untuk menyimpan konten dalam database saya menggunakan struktur berikut: (pageId, bahasa, konten, variabel).

    • pageId akan menjadi FK ke halaman yang ingin Anda terjemahkan. jika Anda memiliki objek lain, seperti berita, galeri atau apa pun, hanya membaginya menjadi 2 bidang objectId, objectTable.

    • bahasa - jelas itu akan menyimpan string bahasa ISO EN_en, LT_lt, EN_us dll.

    • konten - teks yang ingin Anda terjemahkan bersama dengan wildcard untuk penggantian variabel. Contoh "Halo mr. %% nama %%. Saldo akun Anda adalah %% saldo %%."

    • variabel - variabel yang dikodekan json. PHP menyediakan fungsi untuk menguraikannya dengan cepat. Contoh "nama: Laurynas, saldo: 15,23".

    • Anda sebutkan juga bidang siput. Anda dapat dengan bebas menambahkannya ke tabel ini hanya untuk memiliki cara cepat untuk mencarinya.

  3. Panggilan basis data Anda harus dikurangi seminimal mungkin dengan menyalin terjemahan. Itu harus disimpan dalam array PHP, karena itu adalah struktur tercepat dalam bahasa PHP. Bagaimana Anda akan membuat cache ini terserah Anda. Dari pengalaman saya, Anda harus memiliki folder untuk setiap bahasa yang didukung dan array untuk setiap pageId. Cache harus dibangun kembali setelah Anda memperbarui terjemahan. HANYA array yang diubah harus dibuat ulang.

  4. saya pikir saya menjawab itu di # 2

  5. ide Anda sangat logis. ini cukup sederhana dan saya pikir tidak akan membuat Anda masalah.

URL harus diterjemahkan menggunakan siput yang tersimpan di tabel terjemahan.

Kata-kata terakhir

itu selalu baik untuk meneliti praktik terbaik, tetapi jangan menemukan kembali roda. hanya mengambil dan menggunakan komponen dari kerangka kerja yang terkenal dan menggunakannya.

lihat komponen terjemahan Symfony . Ini bisa menjadi basis kode yang baik untuk Anda.


Terima kasih atas komentarnya, berikan +1 untuk waktu Anda. Laravel (dalam kasus saya) menggunakan beberapa bagian Symfony jika saya tidak salah, jadi Anda benar tentang tidak menciptakan kembali roda. Saya memulai pertanyaan ini (dan karunia) untuk mendapatkan wawasan tentang cara orang lain menerjemahkan, saya mulai percaya ada banyak praktik terbaik di luar sana :-)
Joshua - Pendo

1

Saya telah mengajukan beberapa pertanyaan yang berhubungan dengan diri saya sendiri, kemudian tersesat dalam bahasa formal ... tetapi hanya untuk membantu Anda sedikit, saya ingin berbagi beberapa temuan:

Saya merekomendasikan untuk melihat CMS lanjutan

Typo3untuk PHP (saya tahu ada banyak hal tapi itu yang saya pikir paling matang)

Plone di Python

Jika Anda mengetahui bahwa web pada tahun 2013 seharusnya berfungsi berbeda, mulailah dari awal. Itu berarti mengumpulkan tim yang terdiri dari orang-orang yang sangat terampil / berpengalaman untuk membangun CMS baru. Mungkin Anda ingin melihat polimer untuk tujuan itu.

Jika menyangkut coding dan situs web multibahasa / dukungan bahasa asli, saya pikir setiap programmer harus memiliki petunjuk tentang unicode. Jika Anda tidak tahu unicode Anda pasti akan mengacaukan data Anda. Jangan pergi dengan ribuan kode ISO. Mereka hanya akan menghemat memori. Tetapi Anda dapat melakukan segalanya dengan UTF-8 bahkan menyimpan karakter Cina. Tetapi untuk itu Anda harus menyimpan karakter 2 atau 4 byte yang menjadikannya utf-16 atau utf-32.

Jika ini tentang penyandian URL, sekali lagi di sana Anda tidak boleh mencampur penyandian dan menyadari bahwa setidaknya untuk nama domain ada aturan yang ditentukan oleh lobi berbeda yang menyediakan aplikasi seperti browser. misalnya Domain bisa sangat mirip seperti:

ьankofamerica.com atau bankofamerica.com samesamebutdifferent;)

Tentu saja Anda membutuhkan sistem file untuk bekerja dengan semua penyandian. Kelebihan lainnya untuk unicode menggunakan sistem file utf-8.

Jika ini tentang terjemahan, pikirkan tentang struktur dokumen. mis. buku atau artikel. Anda memiliki docbookspesifikasi untuk memahami tentang struktur itu. Tetapi dalam HTML itu hanya tentang blok konten. Jadi, Anda ingin memiliki terjemahan pada level itu, juga pada level halaman web atau level domain. Jadi, jika tidak ada blok, maka tidak ada di sana, jika halaman web tidak ada, Anda akan diarahkan ke tingkat navigasi atas. Jika suatu domain harus sepenuhnya berbeda dalam struktur navigasi, maka .. itu adalah struktur yang sama sekali berbeda untuk dikelola. Ini sudah bisa dilakukan dengan Typo3.

Jika ini tentang kerangka kerja, yang paling matang yang saya tahu, untuk melakukan hal-hal umum seperti MVC (kata kunci saya sangat membencinya! Seperti "kinerja" Jika Anda ingin menjual sesuatu, gunakan kata kinerja dan featureerich dan Anda menjual ... apa sih) Zend. Telah terbukti menjadi hal yang baik untuk membawa standar ke coders php chaos. Tapi, typo3 juga memiliki Framework selain CMS. Baru-baru ini telah dibangun kembali dan disebut flow3 sekarang. Kerangka kerja tentu saja mencakup abstraksi basis data, templating dan konsep untuk caching, tetapi memiliki kekuatan individu.

Jika ini tentang caching ... itu bisa sangat rumit / berlapis-lapis. Dalam PHP Anda akan berpikir tentang accellerator, opcode, tetapi juga html, httpd, mysql, xml, css, js ... segala jenis cache. Tentu saja beberapa bagian harus di-cache dan bagian dinamis seperti jawaban blog seharusnya tidak. Beberapa harus diminta melalui AJAX dengan url yang dihasilkan. JSON, hashbangs dll.

Kemudian, Anda ingin memiliki komponen kecil di situs web Anda untuk diakses atau dikelola hanya oleh pengguna tertentu , sehingga secara konseptual yang memainkan peran besar.

Anda juga ingin membuat statistik , mungkin telah mendistribusikan sistem / facebook facebook dll. Setiap perangkat lunak yang akan dibangun di atas Anda di atas cms ... jadi Anda perlu berbagai jenis database di memori, bigdata, xml, apa pun .

baik, saya pikir itu cukup untuk saat ini. Jika Anda belum pernah mendengar tentang salah ketik / plone atau kerangka kerja yang disebutkan, Anda sudah cukup belajar. Di jalur itu Anda akan menemukan banyak solusi untuk pertanyaan yang belum Anda tanyakan.

Jika menurut Anda, mari buat CMS baru karena 2013 dan phpnya akan mati, maka Anda dipersilakan untuk bergabung dengan grup pengembang lain semoga tidak tersesat.

Semoga berhasil!

Dan btw. bagaimana dengan orang-orang yang tidak memiliki situs web lagi di masa depan? dan kita semua akan berada di google +? Saya harap pengembang menjadi sedikit lebih kreatif dan melakukan sesuatu yang bermanfaat (agar tidak berasimilasi dengan borgle)

//// Edit /// Hanya sedikit pemikiran untuk aplikasi yang ada:

Jika Anda memiliki php mysql CMS dan Anda ingin menyematkan dukungan multilang. Anda bisa menggunakan tabel Anda dengan kolom tambahan untuk bahasa apa pun atau menyisipkan terjemahan dengan id objek dan id bahasa di tabel yang sama atau membuat tabel identik untuk bahasa apa pun dan menyisipkan objek di sana, lalu membuat gabungan pilih jika Anda ingin agar semuanya ditampilkan. Untuk database gunakan utf8 general ci dan tentu saja di depan / backend gunakan utf8 text / encoding. Saya telah menggunakan segmen jalur url untuk url seperti yang sudah Anda jelaskan

domain.org/en/about Anda dapat memetakan ID lang ke tabel konten Anda. lagi pula Anda harus memiliki peta parameter untuk url Anda sehingga Anda ingin menentukan parameter yang akan dipetakan dari pathsegment di URL Anda yang akan menjadi eg

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

konfigurasi pencarian

pageid | url

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

1 | /../tentang/karyawan../../

parameter peta ke url pathsegment ""

$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;
}

per mengatakan, itu sudah dibahas di posisi atas.

Dan jangan lupa, Anda harus "menulis ulang" url ke file php penghasil Anda yang dalam banyak kasus adalah index.php


Terima kasih atas komentarnya, pasti ada beberapa hal yang harus saya pikirkan. Saya telah menggunakan pengkodean utf8 selama beberapa tahun sekarang, saya pernah berjuang dengan karakter sekali ;-) Di sisi lain jenis CMS / Framework dimaksudkan untuk tidak menjadi faktor dalam jawaban Anda karena saya sedang mencari Metode platform independen seolah-olah kita sedang coding dari awal.
Joshua - Pendo

jika Anda benar-benar ingin kode dari awal saya sarankan untuk melihat Dartlang dan polimer. Karena dartlang bekerja di browser dan memiliki dukungan 32 dan 64 bit dan dapat digunakan untuk sebagian besar tujuan di sisi server dan memiliki kompiler dart2js, sangat layak untuk dipelajari. Jika orang berbicara tentang independensi platform, mereka berpikir tentang java ... kita tahu apa artinya itu. Buildprocess ... Saya pikir saya akan menggunakan JSON untuk pertukaran. menghasilkan situs web klien dengan hashbangs dan serveride .. baik lakukan apa pun yang Anda ingin memastikan kolaborasi.
Dr. Dama

Databaselayout dan logika generasi adalah tugas utama. Tidak ada yang akan melakukan itu di sini untuk Anda ... tetapi Ide itu sendiri yang penting. Karena saya tidak peduli tentang lobi tetapi untuk menyelesaikan sesuatu, saya harap Anda dapat membuat model dan berbagi beberapa hal. Saya sedang mengerjakan tugas serupa sekarang. Tapi saya masih dalam perencanaan. Saya mempertimbangkan Typo3 sebagai backend dan membuat struktur klien baru. Pola Multilingual diselesaikan di backend dan akan berbagi informasi dengan cara yang didedikasikan untuk mesin pencari / layanan web. Pokoknya semua sensitif terhadap konteks dan tugas pembangunan berkelanjutan
Dr. Dama

-1

Pekerjaan basis data:

Buat Tabel Bahasa 'bahasa':

Bidang:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Buat tabel di 'konten' database:

Bidang:

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

Pekerjaan Front End:

Ketika pengguna memilih bahasa apa saja dari dropdown atau area mana pun, maka simpan id bahasa yang dipilih dalam sesi seperti,

$_SESSION['language']=1;

Sekarang ambil data dari 'database' tabel database berdasarkan id bahasa yang disimpan dalam sesi.

Detail dapat ditemukan di sini http://skillrow.com/multilingual-website-in-php-2/


1
Ini adalah cara untuk integrasi bahasa sederhana yang diperlukan, apakah Anda bahkan mencoba membaca tulisan lengkap dan memberikan jawaban?
Joshua - Pendo

-2

Sebagai orang yang tinggal di Quebec di mana hampir semua situs berbahasa Perancis dan Inggris ... saya telah mencoba banyak plugin multi-bahasa untuk WP ... jika tidak, satu-satunya solusi yang bermanfaat yang bekerja dengan semua situs saya adalah mQtranslate ... aku hidup dan mati bersamanya!

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


1
yeah well, WP bukan merupakan faktor pertanyaan. Ini bisa menjadi komentar aswel
Joshua - Pendo

-3

Bagaimana dengan WORDPRESS + MULTI-LANGUAGE SITE BASIS(plugin)? situs akan memiliki struktur:

  • example.com/ eng / category1 / ....
  • example.com/ eng / my-page ....
  • example.com/ rus / category1 / ....
  • example.com/ rus / halaman saya ....

Plugin ini menyediakan Antarmuka untuk Terjemahan semua frasa, dengan logika sederhana:

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

maka dapat di-output:
echo translate('my_title', LNG); // LNG is auto-detected

ps Namun, periksa, apakah plugin masih aktif.


3
dan bukan "Holla userio" dalam bahasa Spanyol adalah "Hola Usuario"
bheatcoker

1
Lol Holla userio, itu lucu!
spekdrum

untuk alasan saya tidak tahu bahasa spanyol (hanya contoh yang digunakan), cepatlah buru-buru untuk downvote !! :)
T.Todua

-5

Opsi yang sangat sederhana yang berfungsi pada situs web mana pun tempat Anda dapat mengunggah Javascript adalah www.multilingualizer.com

Ini memungkinkan Anda menempatkan semua teks untuk semua bahasa ke satu halaman dan kemudian menyembunyikan bahasa yang tidak perlu dilihat pengguna. Bekerja dengan baik.


Waspadalah, SEO akan sangat buruk! Plus Anda memuat semua konten sementara Anda hanya perlu bagian dari itu yang merupakan praktik yang sangat buruk.
Hafenkranich

hal-hal aneh bahwa situs ini hanya dalam bahasa Inggris ... mengapa mereka tidak menggunakan solusi mereka ??
eduardo.lopes
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.