[PHP] Meertaligheid

Pagina: 1
Acties:
  • 2.583 views sinds 30-01-2008
  • Reageer

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 10:07
Ik onderhoud een vrij groot webbased projectmanagementsysteem dat is geschreven in PHP en gebruik maakt van een mysql-database. Project informatie wordt in 1 taal opgeslagen maar vaste elementen (labeltjes van velden, foutmeldingen, kortom alles wat met de user interface te maken heeft) is in vier talen beschikbaar.

Er is een functie translate( 'engels tekstje' ) die een select doet op een translate tabel. Als het tekstje gevonden wordt en er is een vertaling dan wordt de vertaalde tekst geretourneerd. Als het tekstje niet gevonden wordt dan wordt er een leeg vertaalrecord voor de actieve taal ingevoegd zodat we een overzicht hebben van stringetjes die nog vertaald moeten worden. Voor het vertalen van strings is een interface op de vertaaltabel beschikbaar. Zo nu en dan gaat er eens iemand fijn nieuwe tekstjes vertalen. So far so good.

Probleem is nu dat voor elk woordje een query wordt gedaan. Een beetje page request levert dan 50-100 select statements op. De database staat op dezelfde server als de applicatie en op zich gaat dat best snel maar ideaal is het niet.

Ik heb voor de grap eens gekeken hoeveel het scheelt in de runtime van een page request als die translate functie geen lookup meer doet. Een page request kan dan tot 40% sneller worden.

Er zijn zo'n 700 tekstjes. Updates van de vertalingen moeten realtime verwerkt worden en beschikbaar zijn.

Dus ik wil die translate functie aanpakken.

Opties:
1. gettext. Ben ik huiverig voor omdat hier nogal wat dingen op de server moeten worden geregeld met locales enzo. Momenteel kan dat (de applicatie draait op een dedicated server) maar ik wil daar bij voorkeur niet afhankelijk van zijn.
2. cachen in sessies. een $_SESSION['translations'] array er op na houden. Het grote nadeel daarbij is dat als er een nieuwe vertaling beschikbaar is of een string is gewijzigd, dat niet in de sessie verwerkt kan worden. Bovendien worden die sessies dan erg groot qua file size en een sessie wordt geserialized op geslagen dus bij elk request moet het dan weer ontserialized worden. En gedurende de runtime van een script heb je dus een vertaaltabel in het geheugen staan.
3. Cachen in aparte vertaalbestanden. Deels dezelfde nadelen als bij 2. Maar er is iets als dbm dat zeer snel hashes op kan vragen. Dit vereist echter een hercompilatie van PHP, wat op zich geen probleem is, behalve dan dat ik nu gebruik maak van Debian en dus de security updates mee krijg. Dat is met een eigen php niet meer zo.

Wat is wijsheid?

Acties:
  • 0 Henk 'm!

  • disjfa
  • Registratie: April 2001
  • Laatst online: 03-07 14:47

disjfa

be

Je kan ook in 1 query alle labels ophalen. 1 query veel ophalen is sneller dan 50 losse queries. Dan kan je verder de labels in een array zetten. En die moet je dan gewoon de labelnaam als index gebruiken.

Toch :?

disjfa - disj·fa (meneer)
disjfa.nl


Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 10:07
disjfa schreef op vrijdag 28 december 2007 @ 12:57:
Je kan ook in 1 query alle labels ophalen. 1 query veel ophalen is sneller dan 50 losse queries. Dan kan je verder de labels in een array zetten. En die moet je dan gewoon de labelnaam als index gebruiken.
Ik weet niet van te voren welke labeltjes ik nodig heb. En alle labeltjes opvragen (het zijn er nu > 700 en worden er meer) lijkt me ook niet echt de bedoeling.

Acties:
  • 0 Henk 'm!

  • storeman
  • Registratie: April 2004
  • Laatst online: 06:52
Ik zat onlangs ook met dit probleem en heb een mooie oplossing gevonden. Gettext sprak me wel aan, maar schijnt wat haken haken en ogen te hebben. Ik heb nu een php-gettext gevonden, deze werkt dus zonder module!

Het valt onder de GNU licentie:

Modbreak:WOEI! Weg...

Post voortaan een linkje, een zipje of whatever, maar niet 3 posts achter elkaar een huge-ass lap code zeg :X


Dit zal de engelse tekst laden, tenzij er een ander .mo bestand geladen is.

Met PO edit kun je je hele projectcode doorlopen en deze zoekt op de standaard gettext functies en op gebruiksfuncties (dus __() en _e() toevoegen). Deze toont exact welke regels er nog vertaalt moeten worden.

Succes!

[ Voor 124% gewijzigd door NMe op 28-12-2007 17:23 ]

"Chaos kan niet uit de hand lopen"


Acties:
  • 0 Henk 'm!

  • Evilbee
  • Registratie: November 2002
  • Laatst online: 08:56
Ik heb ook alle teksten in een database staan. Maar zodra er iets is gewijzigd in de database, wordt dit geëxporteerd naar een .php bestand die dan geinclude wordt. En met de volgende functies wordt dit dan uitgelezen:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
$languageCache = array();

function t() {
  global $languageCache;

  $language = 'en'; // read this from the user settings

  $file = ROOT_DIR.'lang/lang.'.$language.'.inc.php';

  if(count($languageCache)==0 && $language!='en' && file_exists($file)) {

    $lang = array();
    include_once($file);

    $languageCache = $lang;
  }

  $args = func_get_args();

  if(count($args)>0) {

    if(array_key_exists($args[0], $languageCache)) {
      $args[0] = $languageCache[$args[0]];
    } else {
      if(DEBUG==1 && $language!='en') {
        addNewLangKey($args[0]);
      }
    }

    return call_user_func_array('sprintf', $args);
  }

  return null;
}

function addNewLangKey($key) {

  $file = ROOT_DIR.'framework/lang/_new.inc.php';

  if($h = @fopen($file, 'a')) {
    fwrite($h, '$lang[\''.addslashes($key).'\'] = \'\';'."\n");
    fclose($h);
  }

}


En het language bestand staat dan het volgende:
PHP:
1
2
$lang['Add folder under "%s"'] = 'Voeg een map onder "%s" toe';
$lang['Add user to <strong>%s</strong>'] = 'Voeg een gebruiker aan <strong>%s</strong> toe';


En het gebruik hiervan is als volgt:
PHP:
1
2
<?php
echo t('Add folder under "'.$parentName.'"');

LinkedIn - Collega worden?


Acties:
  • 0 Henk 'm!

  • Tsunameh
  • Registratie: Juni 2005
  • Laatst online: 04-08 10:24
disjfa schreef op vrijdag 28 december 2007 @ 12:57:
Je kan ook in 1 query alle labels ophalen. 1 query veel ophalen is sneller dan 50 losse queries. Dan kan je verder de labels in een array zetten. En die moet je dan gewoon de labelnaam als index gebruiken.

Toch :?
lijkt mij ook handiger :)
bijv een table met velden als:
labelid,labelnaam,eng,ned,dui,esp bijv :)

Wat is een ander woord voor synoniem?


Acties:
  • 0 Henk 'm!

  • Xcalibur
  • Registratie: Augustus 2002
  • Laatst online: 23:15
Vroeger zette ik alle tekst altijd in een include met een $lang array, waarbij iedere tekstje een key/value in die array was. Afhankelijk van de taal laadde ik een andere include in. Werkte op zich prima, was alleen wat gezeik met het escapen van quotes enzo :)

Als de tekst uit de database komt gebruik ik altijd een text table, waarin alle tekst in alle talen staat, per een id per pagina etc. Bij iedere pagina doe ik dan dus 1 select op die table, waarbij ik alle teksten voor die pagina in de gewenste taal terugkrijg.

Designer | Developer | Director | Photographer | LARPer | Geek | Male | 39


Acties:
  • 0 Henk 'm!

  • storeman
  • Registratie: April 2004
  • Laatst online: 06:52
Aangezien mijn vorige post dramatisch was ingekort, en geheel terecht overigens, post ik hier nogmaals hoe ik het zou doen (maar dan zonder code).

Zie mijn tutorial: http://www.phphulp.nl/php/tutorials/8/574/

"Chaos kan niet uit de hand lopen"


Acties:
  • 0 Henk 'm!

  • Borizz
  • Registratie: Maart 2005
  • Laatst online: 24-08 20:35
Je kan ook eens naar de PEAR Packages in de categorie Internationalization kijken.
Ik gebruik zelf Translation2, deze bied ook veel mogelijkheden om je strings op verschillende manieren op te slaan (XML, Database, gettext). En biedt ook caching methodes (memcache, filecache).

If I can't fix it, it ain't broken.


Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

rutgerw schreef op vrijdag 28 december 2007 @ 13:03:
[...]


Ik weet niet van te voren welke labeltjes ik nodig heb. En alle labeltjes opvragen (het zijn er nu > 700 en worden er meer) lijkt me ook niet echt de bedoeling.
Tja je zou kunnen kijken of er eerst gekeken kan worden welke label_id's opgehaald moeten worden, die dan in een keer ophalen met een query, en dan pas die labels gaan vullen.

iOS developer


Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 10:07
Bedankt voor alle reacties. Ik denk dat ik toch maar voor DBM ga, voor elke taal 1 database file. dbm zit gelukkig en in tegenstelling tot wat ik eerst dacht WEL meegecompileerd.

Nog even een paar reacties:
BikkelZ schreef op maandag 31 december 2007 @ 12:32:
[...]
Tja je zou kunnen kijken of er eerst gekeken kan worden welke label_id's opgehaald moeten worden, die dan in een keer ophalen met een query, en dan pas die labels gaan vullen.
Dat gaat niet aangezien ik niet van te voren (= op het moment dat er HTML naar de browser wordt gestuurd) weet welke teksten ik nodig ga hebben. Dat is een design fout in de applicatie maar daar moet ik mee leven.
storeman schreef op zondag 30 december 2007 @ 13:39:
Aangezien mijn vorige post dramatisch was ingekort, en geheel terecht overigens, post ik hier nogmaals hoe ik het zou doen (maar dan zonder code).

Zie mijn tutorial: http://www.phphulp.nl/php/tutorials/8/574/
Hierbij wordt uiteindelijk ook een complete vertaaltabel in het geheugen geladen. Dat is voor mij reden om het niet te doen. De oplossing van Evilbee heeft hetzelfde euvel.
Xcalibur schreef op vrijdag 28 december 2007 @ 15:19:
Als de tekst uit de database komt gebruik ik altijd een text table, waarin alle tekst in alle talen staat, per een id per pagina etc. Bij iedere pagina doe ik dan dus 1 select op die table, waarbij ik alle teksten voor die pagina in de gewenste taal terugkrijg.
Dat is bruikbaar als je een meertalig cms hebt. Het concept 'pagina' heb ik echter niet en datgene wat er op lijkt (projectinformatie) hoeft niet vertaald te worden. Alleen de schermelementen worden vertaald (labeltjes enzo)

Acties:
  • 0 Henk 'm!

  • Blaise
  • Registratie: Juni 2001
  • Niet online
Het concept 'pagina' heb ik echter niet en datgene wat er op lijkt (projectinformatie) hoeft niet vertaald te worden.
Je zou ook op module of class kunnen indelen. Ik heb per module, per taal een bestand met alle strings voor die module.

"lang/nl.php" van de module "labels" zou er zo uit kunnen zien:

PHP:
1
2
3
4
5
6
$lang = array_merge($lang, array(
   'AUTO_UPDATE'          => 'Automatisch updaten',
   'ADD_LABEL'            => 'Label toevoegen',
   'ADD_LABEL_CONFIRM_Q'  => 'Weet u zeker dat u dit label wilt toevoegen?',
    ...
));


Daar omheen zit nog een wrapper functie voor sprintf() trucjes.

Acties:
  • 0 Henk 'm!

  • FragFrog
  • Registratie: September 2001
  • Laatst online: 15-09 15:57
rutgerw schreef op vrijdag 28 december 2007 @ 13:03:
Ik weet niet van te voren welke labeltjes ik nodig heb. En alle labeltjes opvragen (het zijn er nu > 700 en worden er meer) lijkt me ook niet echt de bedoeling.
Voor een grote internationale site waar ik momenteel aan werk zitten we op een kleine 5000 teksten in 7 talen. Wat wij doen is alle labels opvragen voor de taal van de huidige gebruiker. Query duurt ongeveer twee keer zo lang als de meeste andere queries (het zijn tenslotte bijna 1000 velden) maar ook niet meer dan dat. Vertalen gebeurt vervolgens door de templateparser die een functie aanroept die in de array als key het label heeft, die haalt'm door een stringparser om dynamische delen te vervangen en geeft'm vervolgens terug.

We hebben lang zitten dubben over de beste oplossing, oorspronkelijk had ik ook iets geschreven wat alle templates recursief parsed en vertalings-labels opzoekt en opslaat, maar dit heeft als grote nadeel dat je dezelfde stukjes text tientallen keren zit in te voeren omdat ze vaak op veel meer plekken voorkomen. Nu delen we texten in in componenten en is het nog een kwestie van even in de componentenlijst kijken of er al een text beschikbaar is.

Wat eigenlijk nog het allermooiste zou zijn is een manier om de text-cache in het werkgeheugen van de webserver op te slaan en te laten staan tussen verschillende requests, ik weet dat het in perl kan maar heb eerlijk gezegd geen idee hoe dit met PHP te bereiken valt 8)7

//edit
Nu ik er weer eens naar loop te zoeken: mem_cache zou precies dit moeten kunnen... Toch maar eens naar kijken :)

[ Voor 5% gewijzigd door FragFrog op 05-01-2008 01:17 ]

[ Site ] [ twitch ] [ jijbuis ]


Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 10:07
FragFrog schreef op zaterdag 05 januari 2008 @ 01:15:
[...]

Nu ik er weer eens naar loop te zoeken: mem_cache zou precies dit moeten kunnen... Toch maar eens naar kijken :)
Dat ziet er ook goed uit. Ook het overwegen waard

Acties:
  • 0 Henk 'm!

  • FragFrog
  • Registratie: September 2001
  • Laatst online: 15-09 15:57
rutgerw schreef op zaterdag 05 januari 2008 @ 16:09:
Dat ziet er ook goed uit. Ook het overwegen waard
Ik heb het hier net geimplementeerd - 2000+ keys inladen duurt ongeveer een seconde of anderhalf, maar dat hoeft slechts 1 keer per dag ofzo. Vervolgens is de parsetijd van 0.97s (alle texten ophalen uit de DB) afgenomen naar 0.66s (alleen de noodzakelijke texten direct uit het geheugen halen) waarbij het vertaalgedeelte nog slechts ~5% van de totale parsetijd in beslag neemt.

Met andere woorden, in plaats van een resourcehog is het gehele vertaalgedeelte nu nog slechts een klein onderdeel geworden terwijl er toch een enorme hoeveelheid data snel beschikbaar is. Het geheugenverbruik? Voor 2000+ items slechts 185kb :) Veel werk? Ook niet, in totaal heb ik amper 10 regels code hoeven te vervangen.

Nu alleen nog onze provider zover krijgen dat'ie memcache aanzwengelt :+

[ Voor 4% gewijzigd door FragFrog op 05-01-2008 17:26 ]

[ Site ] [ twitch ] [ jijbuis ]


Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 10:07
dat extra geheugengebruik (185kb) is dus shared over alle server threads neem ik aan?

Bij mij draait het spul op een dedicated server dus support is geen probleem.

Acties:
  • 0 Henk 'm!

  • FragFrog
  • Registratie: September 2001
  • Laatst online: 15-09 15:57
Correct, dat gehele verbruik is voor een enkele memcache deamon - de feitelijke interface tussen PHP en je werkgeheugen. Bovendien kun je deze deamon ook vanaf andere servers benaderen aangezien deze op een TCP poort luistert naar requests, zodat je eventueel met 1 deamon meerdere servers kan bedienen.

Andere kant kan ook trouwens: je kan middels de memcache class servers toevoegen aan een serverpool waarbij zo te zien random een server uitgekozen wordt om een variabele naar weg te schrijven en vervolgens op te halen. Cache redundancy krijg je hier ook mee: als je alles cached op alle daemons kan er zonder problemen een wegvallen, hij kiest automatisch een server die wel werkt.

[ Site ] [ twitch ] [ jijbuis ]


Acties:
  • 0 Henk 'm!

  • Kalentum
  • Registratie: Juni 2004
  • Laatst online: 10:07
Ik heb even test gedaan. Dit is geen realistische situatie maar toont wel de performance winst.

Ik heb een random set van 2.500 woorden gepakt (sommige worden meerdere keren in 1 sessie opgevraagd) en dus memcache en mysql 2.500 keer gevraagd een bepaalde vertaling te leveren. De tijd is gemeten met PHP's microtime

2.500 queries tegen de mysql database (query cache uit): ongeveer 1 seconde
2.500 queries met mysql query caching: 0.15 seconde
2.500 opvragingen tegen memcached: 0.12 s

Dus dat is sneller dan de query cache van mysql. Dat komt waarschijnlijk door de overhead van mysql en de manier waarop die cache werkt. Gezien de grote hoeveelheid verschillende queries tegen de database verwacht ik bovendien dat die query cache vrij snel uitgeput raakt. Dus memcached it is.
Pagina: 1