[ turshija @ 21.03.2012. 20:38 ] @
Cao,
imam dilemu oko tehnike za razvijanje multilingual sajtova u PHP-u (bez frameworka), pa odlucih da napisem pitanje u vidu jednog lepog referata

Koji je najbolji i najefikasniji nacin ?
Do sada sam radio tako sto u lang folder grunem fajlove tipa:
english.php
serbian.php

pa u njih grunem svaki string kao define:
Code:
<?php
define( '_NAVIGATION_HOME', 'Glavna' );
define( '_NAVIGATION_CONTACT, 'Kontakt' );

//itd
?>


i samo na osnovu izabranog jezika inkludujem odgovarajuci fajl, a u stranicama ehujem i to je to ...
U tom sistemu imam dva problema:
1. tezak je za odrzavanje (dodam novi string u jedan jezik i automatski moram da ga dodajem u sve langove)
2. kada se nesto definise, ne moze da se redefinise u istoj skripti

E sada, naleteo sam na problem 2. kada sam stavio i tekst za email notifikacije u lang, pa sada recimo imam sistem drustvene mreze gde pera (kome je jezik srpski) dodaje simu (kome je jezik engleski) i potrebno je kada pera dodaje simu za prijatelja poslati simi email sa siminim default jezikom, a to slanje emaila se odvija u metodi koju pera poziva, pa je samim tim inkludovan perin default jezik (srpski), tako da sam u toj situaciji imao dve varijante:
1. da napravim tabelu u bazi za queue mailova u kom cu staviti na kom jeziku da se posalje, i da imam cron koji se pokrece i svakim pokretanjem da inkluduje odgovarajuci lang i odradi slanje (komplikovano i bespotrebno)
2. napravim parser koji procita odgovarajuci lang fajl i potrpa sve u niz, pa onda pozovem tekst koji mi treba:
Code:
$lang = parseLang('english');
echo $lang['_NAVIGATION_CONTACT'];

I tako nesto sam uradio, pa mi je palo na pamet da pokusam skroz drugaciji pristup svemu: preko ini fajlova i funkcije parse_ini_file ...
Recimo pri ucitavanju sajta, na pocetku da ucitam ceo language ini fajl jezika koji mi treba, potrpam sve stringove u jedan lang niz i to da koristim u celom projektu.

Ali tu se stvara novo pitanje: koliko je tako nesto efikasno ? Svaka stranica bi na pocetku ucitavala odgovarajuci ini fajl svaki put i trpala sve vrednosti u niz, ali da li je to (i koliko) manje efikasno od inkludovanja php lang fajla i trpanja svega u define ?
U svakom slucaju ce neko keshiranje morati tu da ide, ali ako se sajt sa 10k dnevnih poseta ponasa lepo sa include lang fajla, da li ce se isto lepo ponasati i ako se prebacim na ini fajlove za lang ?

Bilo kakvi predlozi i komentari su dobrodosli
[ doktor83 @ 21.03.2012. 20:59 ] @
A zasto ne stavis sve izraze u niz,a ne ovako sa define... ? Nekako mi je logicnije..
I naravno kada dodas novi izraz ,moras na svim jezicima. Jedino da koristis google translate pa neces morati da definises na svim jezicima posebno :))
[ turshija @ 21.03.2012. 21:10 ] @
Ali na taj nacin necu imati mogucnost ucitavanja dva lang fajla odjednom ?
Sa ini fajlovima mogu napraviti metodice koje ce mi ucitati odvojeno dva langa i drzati u dve zasebne promenljive, pa mogu da koristim onaj koji mi treba...
Mada u svakom slucaju u odnosu na define imaju prednost

Kad god sam trazio na netu multilingual resenja, sva su se svodila na define, ali gledam recimo Joomla ima ini fajlove, samo sto je Joomla previse kompleksna da sada prckam po sourcu da bi video kako oni ucitavaju i manipulisu .ini fajlovima
[ dakipro @ 21.03.2012. 22:01 ] @
Citat:
doktor83: A zasto ne stavis sve izraze u niz,a ne ovako sa define...

samo napravis niz da ima jos jednu dubinu ID jezika. Tako da imas
$lang[RS]['contact'] = "Kontakt";
$lang[EN]['contact'] = "Contact";

I svaki jezik drzis u posebnom fajlu. Po defaultu ucitavas fajl za jezik koji se trenutno koristi, a ako ti terba neki drugi jezik onda samo uvuces fajl za jezik koji ti treba bez da imas konflikte.
Takodje, probaj da koristis funckije za 'vadjenje' svakog prevoda. Recimo ovako
Code:
trans('contact');
trans('contact', $langID)

gde ce ta funckija da ti vrati string za taj jezik. Tako mozes prevode drzati u bazi i kesirati, ako funckija nadje prevod u kesu super, ako ne izvadi iz baze, napravi novi kesh fajl za taj jezik, ucita odgovarajuci jezik i sl.
Posle ako promenis nacin dobavljanja prevoda, samo izmenis jedno jedino mesto u funckiji i miran si.
A inace konstante (ovo sa define) koristis skoro pa nikada za bilo sta sto moze da se menja, jer ces imati prevode tipe "Potrebno je da popunite xy polja" gde ces da menjas deo texta "u letu". Recimo konstantu koristis ako moras da hardkodiras ID jezika, pa znas da je 1 srpski, 2 engl, mada i to ces retko imati razloga da cinis jer ces ih drzati u bazi. Al recimo neki specificni tipovi proizvoda na primer, neam pojma sad generalno da objasnim.
[ turshija @ 21.03.2012. 22:20 ] @
Citat:
dakipro: A inace konstante (ovo sa define) koristis skoro pa nikada za bilo sta sto moze da se menja, jer ces imati prevode tipe "Potrebno je da popunite xy polja" gde ces da menjas deo texta "u letu". Recimo konstantu koristis ako moras da hardkodiras ID jezika, pa znas da je 1 srpski, 2 engl, mada i to ces retko imati razloga da cinis jer ces ih drzati u bazi. Al recimo neki specificni tipovi proizvoda na primer, neam pojma sad generalno da objasnim.


Define sam bez problema koristio i za to, samo sto ga provucem kroz sprintf
(malo modifikovan example #3)
Code:
define('_TEST', 'The %2$s contains %1$d monkeys');
echo sprintf(_TEST, 5, 'house');


A ovo za "dublje" nizove hvala, pokusacu tako nesto da sredim, pa cu videti kako sljaka, u svakom slucaju sam planirao da ceo lang drzim u bazi zbog lakseg odrzavanja i modifikovanja iz admin panela, pa svakom izmenom da generise lang fajl koji ce biti kao cache
[ bantu @ 22.03.2012. 13:04 ] @
Napravi neki xml i u njega smještaj sve izraze. A onda napravi neku klasu koja sadrži logiku za učitavanje i parsiranje xml-a i naravno za vraćanja fraze na odgovarajućem jeziku. Ovu klasu možeš da implementuješ kao singleton i da samo jednom po instanciranju da učitaš i parsiraš file.
E sad to sve možež uvijek i da čuvaš u bazi.

Evo npr. xml...
Code:

<items>

    <item value="welcome_message">
        <message lang="sr" default="true">
            Dobrodošli
        </message>
        <message lang="en">
            Welcome
        </message>
        <message lang="fr">
            Accueil
        </message>
    </item>
    
    <item value="error_message">
        <message lang="sr" default="true">
            Greška u sistemu
        </message>
        <message lang="en">
            System error
        </message>
        <message lang="fr">
            Erreur systeme
        </message>
    </item>
    
</items>
[ bantu @ 22.03.2012. 13:11 ] @
Evo brzinski primjera kako da to sve učitaš.
http://www.php.net/manual/en/function.xml-parse-into-struct.php
[ turshija @ 22.03.2012. 15:21 ] @
Hvala na odgovoru bantu, ali opet mi je problem u tome sto je parsiranje tog XML fajla sigurno mnogo zahtevnije od obicnog ucitavanja php fajla sa nizom koji ima dodatnu dubinu/indeks jezika (kao sto je dakipro predlozio), i opet dobijam isto, osim ako ne predlazes da ceo jezik drzim u tom XML fajlu, pa posle parsiranja da kreiram zasebne lang fajlove, ali u tom slucaju mi vise odgovara da cuvam sve u bazi sto cu najverovatnije i uraditi
[ ivan.a @ 22.03.2012. 21:35 ] @
Mislim da je najbolje rešenje da podatke čuvaš u bazi, a da koristiš keširane fajlove.
Ne znam koliko jezika planiraš da dodaješ, ali ako hoćeš fleksibilnost mislim da će ti trebati bar 2 tabele.
1 tabela - "jezici"
-polja: id, naziv_jezika, kod_jezika, aktivan (opciono)
2 tabela - "jezici_definicije"
-polja: kod_jezika, definicija, prevod

(mada se može koristiti i samo jedna tabela sa poljima: `jezik`, `kod_jezika`, `definicija`, `prevod`)

Primer (2 tabele):
jezici
1, Srpski, rs, 1
2, Engleski, en, 1

jezici_definicije
'rs', 'HOME', 'početak'
'rs', 'CAT', 'kategorije'
'en', 'HOME', 'home'
'en', 'CAT', 'categories'

U administratorskom delu snimiš sve definicije (za svaki jezik posebno) u fajl (lang_rs.php, lang_en.php itd.) i to koristiš.
Ovakav pristup može biti praktičan jer je moguće dodavati više jezika, nove definicije itd. Takođe, može biti zgodno da napraviš sistem koji je proveravati da li nedostaje prevod za neki jezik.
Možda je cimanje napraviti sve to, ali zato se lako ažurira.
[ turshija @ 22.03.2012. 23:03 ] @
Pozdrav,
bas za to sam se i odlucio, ali moje prvobitno pitanje je bilo u kom formatu taj cache da cuvam ?

Nekako mi deluje da je najefikasnije resenje ovako nekako:

ime_jezika.php
Code:
<?php
$lang['_MENU_HOME'] = "Glavna";
$lang['_MENU_CONTACT'] = "Kontakt";
...
[ bantu @ 23.03.2012. 10:46 ] @
A zašto se bojiš programiranja, zašto misliš da je parsiranje komplikovano? Sve se riješi u max 30-tak linija koda. Evo ti potpuno fukncionalan primjer. Riješi još samo slučajeve ako dođe do greške u parsiranju i ako nema jezika ili fraze u nizu.

I da klasa je realizovana kao singleton, što će reći da se xml učitava i parsira samo jednom per request-u, kad se kreira instanca, zato sam ti ostavio ove echo-e u Language klasi da vidiš, za produkciju ih izbaci.

Nadam se da ti je pomoglo.

Language.class.php
Code:

<?php
class Language {

    private static $instance;
    private $lang;

    private function __construct() {
        echo 'Parsiram XML<br/>';
        $this->lang = $this->parse();
    }

    public static function getInstance() {    
        if (!isset(self::$instance)) {
            echo 'Kreiranm novu instancu<br/>';  
            self::$instance = new Language();
        }
        return self::$instance;
    }
    
    public function getFraza($fraza, $l) {    
        return $this->lang[$fraza][$l];
    }
    
    private function parse() {
        $xmlDoc = new DOMDocument();
        $xmlDoc->load("lang.xml");

        $x = $xmlDoc->getElementsByTagName('item');

        $niz = array();
        foreach ($x as $item) {
            $fraza = $item->attributes->getNamedItem('value')->nodeValue;
            
            $podniz = array();
            foreach ($item->getElementsByTagName('message') as $child) {
                $lng = $child->attributes->getNamedItem('lang')->nodeValue;
                $message = $child->nodeValue;
                $podniz[$lng] = $message;
            }    
            $niz[$fraza] = $podniz;
          }
          
          return $niz;
    }

}
?>


index.php
Code:

<?php
require_once('Language.class.php');


echo Language::getInstance()->getFraza('welcome_message','sr').'<br/>';
echo Language::getInstance()->getFraza('welcome_message','en').'<br/>';
echo Language::getInstance()->getFraza('welcome_message','fr').'<br/>';
echo Language::getInstance()->getFraza('error_message','sr').'<br/>';
echo Language::getInstance()->getFraza('error_message','en').'<br/>';
echo Language::getInstance()->getFraza('error_message','fr').'<br/>';

?> 


lang.xml
Code:

<?xml version="1.0" encoding="UTF-8"?>
<items>

    <item value="welcome_message">
        <message lang="sr" default="true">
            Dobrodosli
        </message>
        <message lang="en">
            Welcome
        </message>
        <message lang="fr">
            Accueil
        </message>
    </item>
    
    <item value="error_message">
        <message lang="sr" default="true">
            Greska u sistemu
        </message>
        <message lang="en">
            System error
        </message>
        <message lang="fr">
            Erreur systeme
        </message>
    </item>
    
</items>
[ dakipro @ 23.03.2012. 11:34 ] @
Opet bih glasao za bazu, jer mozes da u funckiji
trans('home');
uradis nesto ako nema izraza u bazi, tj nema prevoda, recimo da flagujes string sa revision_needed, i posle samo izlistas sve iz baze sto je ne prevedeno za taj jezik. Tako da ne moras da za svaku labelu ides u sve fajlove i dodajes jednu po jednu i time gubis vreme. Ovako samo pozivas sta ti treba, i kad ti zatreba exportujes sve sto je neprevedeno i posaljes nekom na prevod. Posle samo insert nazad i tolko.
Jednostavno imas najvise flexibilnosti i mogucnosti sa bazom za razliku od alternativa. Jednostavan export, import, filtering, update...
[ turshija @ 23.03.2012. 11:41 ] @
Citat:
bantu: A zašto se bojiš programiranja, zašto misliš da je parsiranje komplikovano?


Ne plasim se ja programiranja
Plasim se opterecenja i "cene" parsiranja XML-a u odnosu na ucitavanje PHP fajla u kojem je vec spreman niz sa jezicima, zato sto radim refactoring sajta koji ima trenutno 10k poseta dnevno, ali ima gomilu stranica, svakog clana zanima neka druga i svaka mora biti sveza jer pokazuje realtime informacije, samo neke stvari mogu da keshiram, zato i razvijam svoj framework u kojem sam implementirao keshiranje zasebnih view-ova i razlicita vremena keshiranja (npr, u stranici neki view mi je keshiran na 10 minuta, neki na 5, a neki na 1) ... Taj sajt takodje generise banere (cache 5 min) i grafike (cache 30 min) koje ljudi koriste po sajtovima i forumima (koji sami po sebi imaju gomilu poseta), pa ima oko 20 miliona hits svaki dan, a planirano je da i baneri budu multilingual, tako da zamisli svi odjednom kada krenu da loaduju to, i za svako ucitavanje se stvara nova instanca ukoliko nije keshiran i novo parsiranje xml-a

U svakom slucaju sve ce raditi na tri zasebna servera za sada: jedan za statican sadrzaj (ceo assets folder), drugi za banere i grafike, a treci za ceo sajt, tako da ce opterecenje biti raspodeljeno, ali sve ce to i dalje biti haos ukoliko svaka instanca bude morala da parsira XML fajl od preko hiljadu linija ...

Citat:
dakipro: Opet bih glasao za bazu, jer mozes da u funckiji
trans('home');
uradis nesto ako nema izraza u bazi, tj nema prevoda, recimo da flagujes string sa revision_needed, i posle samo izlistas sve iz baze sto je ne prevedeno za taj jezik. Tako da ne moras da za svaku labelu ides u sve fajlove i dodajes jednu po jednu i time gubis vreme. Ovako samo pozivas sta ti treba, i kad ti zatreba exportujes sve sto je neprevedeno i posaljes nekom na prevod. Posle samo insert nazad i tolko.
Jednostavno imas najvise flexibilnosti i mogucnosti sa bazom za razliku od alternativa. Jednostavan export, import, filtering, update...


Yep, izgleda ce tako i da bude
U svakom slucaju mi je lako napraviti aplikaciju za prevodjenje koja ce mi lepo markirati neprevedene stringove i prikazati za koji jezik fale
[ bantu @ 23.03.2012. 14:32 ] @
20M hits svaki dan, to je baš puno. Jesi li razmišljao o memcached ili nečemu sličnom, za ovakve stvari?
[ turshija @ 23.03.2012. 18:09 ] @
Citat:
bantu: 20M hits svaki dan, to je baš puno. Jesi li razmišljao o memcached ili nečemu sličnom, za ovakve stvari?


Razmisljao, ali ostavio za "jednom u buducnosti", posto jos nisam imao prilike da radim sa memcached-om
Ja se i dalje cudim kako ovaj sajt izdrzava tih 20M hits, sto je stvarno jako mnogo, takodje uzimajuci u obzir cinjenicu da sam sajt radio pre 3 godine kada sam bio bas pocetnik, pa sam samo vremenom doradjivao lose stvari i na kraju odlucio da odradim totalni refactoring jer mi je muka da gledam svoj stari spaghetti code

Uglavnom, trudim se da u napred razmisljam o vecini stvari, zato sam odmah zeleo da pitam za savet kod lang fajlova, jer znam da bi parsiranje xml-a ili ini-ja verovatno bilo "skupo" ukoliko se posete i hits-ovi utrostruce (ili udesetostruce ) itd ...

Keshiranje mi je bio veliki problem, jer kao sto rekoh, sajt ima jako puno stranica (u pitanju je gametracker.rs), trenutno ima preko 6000 servera, svaki server ima svoju stranicu, 3 banera u razlicitim velicinama, 3 razlicita grafika (dnevni, nedeljni, mesecni), citanje igraca iz baze, rankovanje svih servera itd, da ne spominjem 32k registrovanih clanova, vlasnika tih game servera itd, tako da cak i sa najboljom idejom za keshiranje, opet imam veliko opterecenje jer svakog posetioca interesuje neka druga stranica, drugi server i ucitace neki drugi baner koji se nalazi na nekom tamo levom sajtu/forumu koji je ko zna koliko posecen i stoji nekoj osobi u signaturi...

Ali verujem da sam odradio super stvar sa keshiranjem, jer sam neke stvario sredio do te mere, da za stranice koje ljudi cesto posecuju (home page, prvih par stranica liste servera) konekciju na bazu fakticki pravim jednom na svakih par minuta, dok na trenutnom sajtu te stranice ni ne keshiram, a sve radi solidno (nekad uspori kad skoce posete) i dosta sam zadovoljan sa tim, mada opet imam osecaj da kada za godinu - dve pogledam i ovaj novi kod da cu reci "lele, kako sam bio glup"
[ ivan.a @ 26.03.2012. 12:45 ] @
Citat:
turshija: Pozdrav,
bas za to sam se i odlucio, ali moje prvobitno pitanje je bilo u kom formatu taj cache da cuvam ? :)

Nekako mi deluje da je najefikasnije resenje ovako nekako:

ime_jezika.php
Code:
<?php
$lang['_MENU_HOME'] = "Glavna";
$lang['_MENU_CONTACT'] = "Kontakt";
...
Ja bih tako uradio ili npr. DEFINE("_MENU_HOME", "Glavna"); s' tim što bi tada trebalo da proveravaš da li postoji definicija if ( defined("_MENU_HOME") ) prilikom unosa...
Kod svega ovoga bi najveći problem bio uslađivanje definicija iz baze i u .php fajlovima (korišćenje). Za ovo je verovatno potrebno najviše cimanja. Možda bi mogao da razmišljaš o parseru koji bi učitavao sve .php fajlove u kojima se koristi prevod i odatle izvlačio definicije za bazu (može se koristiti preg_match metoda).
Na primer parser u fajlu nađe $lang['_MENU_HOME'] = "Glavna"; i odatle "izvuče" _MENU_HOME. U administraciji bi npr. imao opciju "Reload language definitions" čime se startuje parser. Ako te ne mrzi možeš uraditi editovanje i upis setovanih definicija iz fajlova. Npr. zameniš definiciju _MENU_HOME sa HOME_ i upišeš u sve fajlove gde se koristi (i u bazu). Za ovo ti treba odlično znanje za regularne izraze, manipulaciju stringovima i fajlovima.
Davno sam radio sličan koncept za "multi-language" sistem pa mi je zato palo na pamet...
[ zmil @ 26.03.2012. 15:20 ] @
Komplikujete
najprostije resenje
sve na srpski
Code:

<?php
$_SESSION['language']='en';

 echo _("neka rec");

?>

prikljucis u kodu ovu klasu kreiras po i mo fajlove imas poedit
i to je to
http://stackoverflow.com/quest...4449/how-to-generate-a-po-file
a ima ih i WordPress

http://blog-en.icanlocalize.co...emes-and-plugins-with-gettext/
Code:
<?php

/**

* @desc Class for translating 
*/

class Translate {

    function __construct() {



      if (isset($_GET['language'])) {

        $language = $_GET['language'];

      } else {

        if (isset($_SESSION['language'])) {

            $language = $_SESSION['language'];

        } else {

        if(defined("LANG")){

         $language = LANG; 

        }else{

        $language ="sr" ;// default language set in config file }

           

        }

            

        }

      }

        switch ($language) {

            case 'sr':

            $language = 'sr';

            $language_code = 'sr_CS';

            break;

            case 'hr':

            $language = 'hr';

            $language_code = 'hr_HR';

            break;



            case 'en':

            $language = 'en';

            $language_code = 'en_US';

            break;

            

            case 'nl':

            $language = 'nl';

            $language_code = 'nl_NL';

            break;

            default:

             $language = 'sr';

            $language_code = 'sr_CS';



        }



        $_SESSION['language'] = $language;



        if ((ini_get('safe_mode') == FALSE) && (getenv('LC_ALL') != $language_code)) {

            putenv('LC_ALL=' . $language_code);

        }

        setlocale(LC_ALL, $language_code);

        bindtextdomain('messages', ROOT . DS . 'locale/');

        bind_textdomain_codeset('messages', 'UTF-8');

        textdomain('messages');

    }

}

?>