netcoffee.pl*po godzinach - reaktywacja

Ten blog jest kontynuacją bloga dostępnego ongiś pod adresem netcoffee.pl/pogodzinach.
Artykuły, które na to zasługują są przenoszone do nowej wersji bloga. Pozostałe wkrótce znikną.

2006-01-19

Wykrywanie języka użytkownika

Często pojawia się pytanie, jak - w wielojęzycznych serwisach - ustalić język, w którym powinna pojawić się zawartość strony. Przeszukując Internet można znaleźć kilka rozwiązań:
  • ustalać język na podstawie adresu IP
  • ustalać język na podstawie nazwy domenowej hosta
  • ustalić język użytkownika na podstawie informacji o wersji językowej przeglądarki

Niestety, każda z powyższych metod ma swoje wady: użytkownik może łączyć się przez serwer proxy, więc adres IP nie jest miarodajny, może używać przeglądarki w innej wersji językowej, niż jego język itd. Te wady można by wymieniać, ale nie o to tutaj chodzi.

Najlepszą moim zdaniem metodą wykrycia języka użytkownika, jest ustalenie go na podstawie nagłówków http wysyłanych przez przeglądarkę. Każda nowoczesna przeglądarka pozwala użytkownikowi wybrać w ustawieniach programu preferowane języki. Na wybranie języków pozwala użytkownikowi nawet Internet Explorer. Wybór ten jest następnie wysyłany do serwera w postaci nagłówka Accept-Language.

Zawartość tego nagłówka ma postać listy dwuliterowych kodów języków rozdzielonych znakiem przecinka, np:

pl,en

Kod języka może być także uzupełniony o oznaczenie dialektu:

pl,en-gb

Ten zapis oznacza, że użytkownik zna język polski i angielski (brytyjski), ale preferuje ten pierwszy.

W przypadku podania kilku języków, każdy z nich może być oznaczony dodatkowym parametrem q (quality). Parametr ten oznacza wagę danego języka. q może przyjmować wartości od 0 do 1. 0 oznacza język ignorowany. Waga języka jest zapisywana jako q=xx i oddzielona od kodu języka średnikiem (;):

pl,en;q=0.9,ru;q=0.8

Jeżeli dany język nie posiada wagi, przypisuje mu się domyślnie wagę 1. W powyższym przykładzie wagi języków wynoszą:
  • polski - 1
  • angielski - 0.9
  • rosyjski - 0.8

W PHP zawartość nagłówka Accept-Language jest dostępna w zmiennej $_SERVER['HTTP_ACCEPT_LANGUAGE'].Przy użyciu niezbyt skomplikowanej funkcji można określić język, w którym należy wyświetlić zawartość strony:


function getLanguage($sDefault, $ihSystemLang){
$sLangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
preg_match_all(
'!([a-zA-Z]+)(?:-[a-zA-Z]+)?(?: *; *q *= *([01]\.[0-9]+))?!',
$sLangs, $shFound);
foreach($shFound[1] as $i => $sLang){
$iW = (float)$shFound[2][$i];
$ihUserLang[$sLang] = $iW > 0 ? $iW : 1;
}
$iChoiceWeight = 0;
$sChoiceLang = '';
foreach($ihSystemLang as $sLang => $iW){
if(isset($ihUserLang[$sLang])){
$iTmpChoice = $iW * $ihUserLang[$sLang];
if($iTmpChoice > $iChoiceWeight and $iTmpChoice > 0){
$iChoiceWeight = $iTmpChoice;
$sChoiceLang = $sLang;
}
}
}
return $sChoiceLang != '' ? $sChoiceLang : $sDefault;
}


Funkcja przyjmuje dwa parametry: kod domyślnego języka oraz tablicę asocjacyjną dostępnych na stronie języków wraz z ich wagami. Funkcja zwraca kod języka wybranego na podstawie ustawień przeglądarki. W czasie wyznaczania języka, ignorowane jest oznaczenie dialektu (gdyż niepotrzebnie komplikuje wybór).

Jeżeli strona jest dostępna w języku polskim i angielskim oraz posiada częściowe tłumaczenie na język francuski, wywołanie funkcji może przyjąć postać:

$sLang = getLanguage('pl', array('pl' => 1, 'en' => 0.9, 'fr' => 0.4));

Językiem podstawowym serwisu jest polski, dlatego właśnie ten język jest podany jako domyślny (pierwszy parametr funkcji) oraz na liście wag otrzymuje 1. Język angielski otrzymuje wysoką wagę 0.9, natomiast francuski tylko 0.4 (ze względu na to, że tłumaczenie jest tylko częściowe).

Wynik działania funkcji zależy od nagłówka Accept-language. Przykładowe wyniki:

Accept-language: pl,en
wynik: pl
Accept-language: fr,en
wynik: en
Accept-language: fr,en;q=0.3
wynik: fr

W przypadku, gdy w nagłówku nie ma żadnego z języków obsługiwanych przez stronę, funkcja zwróci kod domyślny (podany jako pierwszy parametr).

Na koniec kilka słów wyjaśnienia, dlaczego uważam metodę wyboru języka na podstawie nagłówka Accept-language za najlepszą. Oczywiście, jest ona tak samo zawodna jak te, które wymieniłem wcześniej. W końcu, użytkownik może korzystać z przeglądarki w której domyślnie jest ustawiony język inny niż język, którym posługuje się użytkownik. Może używać komputera w pracy za granicą itd.
Jednak powyższa metoda jest jedyną z wymienionych, w której do wykrycia języka używamy parametru, nad którym użytkownik ma całkowitą kontrolę. Przecież nie zawsze możemy wpływać na adres IP z którego łączymy się z siecią (przebywamy za granicą, używamy serwera proxy itp). Możemy używać ulubionej przeglądarki w wersji językowej innej niż nasz język gdyż nie jest jeszcze dostępne tłumaczenie. Nie zmienimy wersji językowej, ale preferowane języki możemy zmienić!

Oczywiście trudno wymagać od użytkowników aby wybierali język strony poprzez ustawienia przeglądarki - dlatego więc nie należy zapominać o udostępnieniu użytkownikowi możliwości zmiany języka na wypadek, gdyby funkcja dokonała złego wyboru.

2006-01-15

Optymalizacja bazy MySql - część II

Wcześniej (Prędkość czy miejsce) pisałem, jak można zaprojektować tabelę MySQL aby baza działała szybciej lub zajmowała mniej miejsca. Teraz przedstawiam garść innych porad:

używaj pól o jak najmiejszym rozmiarze
zmniejszenie rekordu redukuje ilość operacji dyskowych. Jeżeli przechowujesz hasło w postaci hasza md5, użyj pola char(32) a nie varchar(100)
deklaruj kolumny jako NOT NULL
zmniejszysz rozmiar rekordu
dla tabel MyISAM używaj rekordu o stałej długości
patrz: Prędkość czy miejsce
podstawowy indeks tabeli powinien być tak mały, jak to możliwe
przyspieszy to przeszukiwanie i tworzenie pliku indeksu
twórz indeksy których naprawdę potrzebujesz
tworzenie i odczytywanie indeksu też zajmuje czas. Indeksuj te kolumny, których używasz w części WHERE, ORDER BY lub GROUP BY zapytania. Nie musisz indeksować kolumn, których używasz tylko w części SELECT
pierwsza kolumna złożonego klucza powinna być kolumną najczęściej używaną
przyspieszy to przeszukiwanie indeksu
jeżeli pobierasz wiele kolumn z tabeli, najpierw użyj kolumny posiadającej wiele duplikatów
jeżeli kolumna ma unikalny prefiks na kilku pierwszych znakach, lepiej jest zindeksować tylko te kilka pierwszych znaków
użyj do tego funkcji MySQL pozwalającej na tworzeniu indeksu lewej części kolumny znakowej
w pewnych warunkach może przydać się podzielenie tabeli na dwie
w ten sposób możesz uzyskać w jednej tabeli rekordy o stałej długości
indeksy działają wydajniej dla kolumn, które mają dużą ilość unikalnych wartości w stosunku do ilości rekordów (ang. cardinality)
lepiej działa indeks dla kolumny 'login' (wszystkie wartości unikalne) niż dla kolumny 'wojewodztwo' (tylko 16 różnych wartości)
zwróć uwagę na typ pól podczas porównywania
jeżeli wyszukujesz rekordy na podstawie pola typu INTEGER, nie używaj porównania tego pola z typem STRING (np. WHERE id = '78'). Lepiej porównuj pola tego samego typu (np. WHERE id = 78). Jeżeli porównujesz wartość pola z wartością innego pola, zadbaj aby miały one ten sam typ. Dzięki temu unikniesz konieczności rzutowania. Pamiętaj także, że int i bigint to inne typy pól oraz char(10) nie odpowiada polu char(12).
staraj się umieszczać indeksowane samotnie po jednej stronie porównania
w przeciwnym wypadku MySQL nie będzie mógł użyć indeksów. Lepszą wydajność uzyskasz pisząc 'WHERE i < 4 / 2' niż 'WHERE i *2 < 2'
nie używaj znaków wieloznacznych na początku porówanania LIKE
używaj pól typu ENUM jeżeli to możliwe