Nerdblog.pl - Wielojęzykowa strona w Kohanie 3.x

Wielojęzykowa strona w Kohanie 3.x

Dodano: 21.06.2011

Podczas pracy nad jednym projektem dostałem zadanie - zrobić stronę w wielu językach. Ponieważ problem jest dość ciekawy, postanowiłem trochę o nim popisać[1]

W przypadku tworzenia mniej dynamicznej strony internetowej zawierającej jednak panel administracyjny, możemy mówić o tłumaczeniu dwóch najważniejszych elementów: tych w bazie danych i tych na stronie. Na początek skupmy się na tych na stronie, a później przejdziemy do bazy danych.

Strona - tak, to trzeba przetłumaczyć

Ponieważ Kohana jest lekkim i mocno modyfikowalnym frameworkiem, do każdego zadania możemy podejść na kilka sposobów. Ma to swoje wady - praca z innym developerem potrafi być upierdliwa jeśli mamy już swoje 'sprawdzone' metody, ale ma też wiele zalet, choćby to, że można dobrze doprofilować sobie narzędzia do potrzeb. Tak samo jest z wielojęzykowością[2]. Domyślnie Kohana do tego typu zadań posiada klasę i18n, która pozwala na wykorzystanie __() (gettext). Przykładowo po utworzeniu odpowiednich plików językowych fraza

__("Hello world!")

Może zostać wyświetlona jako Hello world! w języku angielskim oraz Witaj świecie! w języku polskim.

To rozwiązanie niestety nie nadaje się do stron internetowych. Możecie mnie oczywiście zlinczować, skoczcie po smołę, a ja rozwalę kilka poduszek, ale taka jest prawda. Większe bloki tekstu bardzo niewygodnie modyfikuje się tym sposobem, ponieważ drobna zmiana składni pociąga za sobą modyfikację tego samego w N plikach (gdzie N to ilość języków).

Cwani programiści rozwiązują to na przykład w ten sposób:

__("main_page.description")

To rozwiązanie też jest paskudne. Dlaczego? Ponieważ wymaga od nas pamiętania --nastu albo nawet --set słów-kluczy. Ciężko potem się połapać gdzie co jest i jak właściwie do cholery miało działać. Nadaje się to na niewielką stronę, ale przy większym projekcie, a już w ogóle modułowym zaczyna robić się burdel. Poza tym co jeśli ma to przyjmować jakieś wyrazy lub zmienne liczbowe? Nagle tworzy nam się tona dodatkowych podkluczy + wersje językowe (np. 1 bułka / 2 bułki / 5 bułek) itd.

Więc jak to ugryźć?

Wspominałem już, że Kohana jest modyfikowalna, prawda? W takim razie się zabawimy. Stworzymy sobie N widoków na strony zawierających odpowiednio przetłumaczone i poprawnie obsługujące wielojęzykowość podstrony, a żeby się nie bawić z doklejaniem co chwilę I18n::lang()[3] zmodyfikujemy nieco klasę View.

Schody? Nie, raczej winda.

Jeśli jesteś początkujący w Kohanie, powyższe wyda ci się przerażające. Ale jak to, modyfikować libkę należącą do core Kohany? Uspokajam - system/ pozostanie nieruszone, pamiętacie, kaskadowy system plików, no nie? ;)

Zacznijmy od utworzenia pliku application/classes/view.php i wpisania do niego podstawowego szkieletu:

<?php class View extends Kohana_View
{
 
}

Ten brak ?> to nie błąd, to konwencja zapożyczona z Fuela. Możesz też zwrócić uwagę, że nie trzymam się do końca Kohanowskiej konwencji i robię enter przed otwarciem klamerki - tak, ugryź się, takie Javowanie jest nieczytelne.

Wracając do tematu - musimy się teraz chwilę zastanowić [4] do czego dążymy. Chcemy, żeby klasa przy pobieraniu widoku szukała czy istnieje wersja językowa i jeśli tak pobierała ją. Strukturę katalogów w view/ możemy więc utworzyć dla uproszczenia jako

view
  - view/en-us
  - view/pl-pl

Teraz myślimy jeszcze raz, aż się mózg grzeje - w którym momencie powinniśmy doklejać ścieżkę? Doklejanie jej bezczelnie do nazwy widoku spowoduje niewyświetlenie go w przypadku gdy coś będzie niedotłumaczone, a przecież chcemy mieć ładny fallback, prawda?

Nurkujemy więc w dokumentację i kod źródłowy i znajdujemy - View::set_filename() [5]. Metoda ta korzysta z Kohana::find_file() do znajdowania odpowiedniego pliku i zwraca jego ścieżkę. Do naszych celów będzie idealna. Otwieramy więc nasze application/classes/view.php i piszemy:

protected static $default_lang = "en-us";
 
public function set_filename($file)
{
   if (($path = Kohana::find_file('views', I18n::lang().'/'.$file))       === FALSE &&
       ($path = Kohana::find_file('views', self::$default_lang.'/'.$file)) === FALSE)
   {
      throw new Kohana_View_Exception('The requested view :file could not be found', array(
         ':file' => $file,
      ));
   }
 
   $this->_file = $path;
   return $this;
}

Co właśnie zrobiliśmy? Jeśli masz jakiekolwiek pojęcie o PHP prawdopodobnie już widzisz, dla reszty czytającej do dla funu - utworzyliśmy sobie zmienną zawierającą domyślny język [6] i dodaliśmy sprawdzanie czy plik istnieje dla danej wersji językowej.

Proste, co?

...

Nie no, chyba nie myślisz, że tak to zostawimy? Bez żartów, to nie jest kolejny nastoletni blogasek o pr0 programowaniu, żeby zostawiać tego typu half-assed rozwiązania.

Zapewne do tej pory zdążyłeś już wyczuć wszystkie możliwe wady tego rozwiązania i ostrzysz już klawiaturę, żeby mi dowalić. Przede wszystkim - nie zostawiliśmy miejsca na szablony, które są wspólne dla obu wersji językowych (można je wrzucać po prostu do en-us, ale to bardzo nieeleganckie rozwiązanie). Poza tym każda zmiana struktury strony powoduje modyfikację N podstron. Brudne, niewygodne itd.

Więc po co właściwie pokazałem to rozwiązanie? Żeby nakierować na właściwe tory ;)

Moglibyśmy jeszcze bardziej przerobić set_filename, dodać jeszcze jeden fallback, tym razem do domyślnego katalogu. [7], ale to nie rozwiąże problemu struktur. Nie, możemy zrobić coś lepszego.

Wykorzystać widoki w roli czystego i18n.

Tłumaczenie w widoku?

Sposób rozwiązania jest banalny. Wycinamy w cholerę nasze set_filename (chciałem tylko pokazać, że się da tak zrobić) i wracamy do punktu wyjścia. Tym razem tworzymy jednak pełnoprawną, osobną metodę w klasie View, która posłuży nam jako punkt bazowy do tłumaczeń, tym razem bazując jednak zarówno na View::set_filename() jak i View::factory():

public static function i18n($file = NULL, array $data = NULL)
{
   if ((Kohana::find_file('views', I18n::lang().'/'.$file)) !== FALSE)
   {
      return new View(I18n::lang().'/'.$file, $data);
   }
   else
   {
      return new View(self::$default_lang.'/'.$file, $data);
   }
}

Teraz mamy ładną statyczną metodę, która pobierze nam odpowiedni element z języka, zwróci fallback jeśli istnieje lub po prostu wywali się (całkiem słusznie) z błędem jeśli nie znajdzie ani jednego, ani drugiego. Możesz sobie dodać jeszcze taki myk przy zwracaniu fallbacka, żeby wykrywać problemy na przyszłość:

Log::instance()->add(Log::WARNING, 'Translation for :lang for file doesn't exist: :file', array(
   ':lang' => I18n::lang(),
   ':file' => $file
));

Ostatnie szlify

Pozostaje jedna kwestia - jak tłumaczyć pojedyncze elementy? Przecież nie będziemy tworzyć całego nowego widoku gdy musimy w szablonie przetłumaczyć np. przycisk "Zaloguj się" na górze strony. W tej kwestii sprawdza się już po prostu gettext. Wiadomo, że mając w ręku młotek wszystko wygląda jak gwóźdź, ale warto dopasowywać narzędzia do potrzeb.

A jeśli komuś przeszkadza, że ma część tekstu w view/, a część w i18n/ - opakuj to sobie w moduł. W modules tworzysz katalog, np translations/, po czym wrzucasz tam po kolei katalogi PL, EN itd, a w bootstrap.php dodajesz je jako moduły:

'translation_en'  => MODPATH.'translation/EN',  
'translation_pl'  => MODPATH.'translation/PL',  

Kaskadowy system plików to fajna sprawa, co? :)[8]

Pozostaje jeszcze jedna sprawa:

Wielojęzykowość danych w bazie

Na ten problem filozofowie i programiści szukali rozwiązania od lat: jak sprawić, żeby pola w bazie były w wielu językach? Istnieją trzy podstawowe filozofie:

Pierwsza to osobna baza danych - strony są od siebie pod tym względem niezależne i mogą zawierać różne dane. Względnie podwersją tego są różne nazwy tabel, ale idea jest identyczna. Powoduje to jednak problemy gdy dane mają mimo wszystko być takie same - łatwo to wszystko rozsynchronizować i generalnie jest paskudnie.

Druga to osobne klucze w bazie. Zamiast N tabel o nazwie tabela_LANG mamy N kluczy w stylu name_LANG1, name_LANG2, name_LANG3, description_LANG1, description_LANG2, description_LANG3. Dawniej stosowałem to rozwiązanie, ale jest bałagarniaskie jak diabli. Przede wszystkim struktura bazy się komplikuje, poza tym nieprzyjemnie się pracuje przy więcej niż dwóch językach.

Ostatnie rozwiązanie zaproponował mi bodajże ^argasek, ale nieco je zmodyfikowałem: osobna tabela zawierająca tłumaczenia. Można do tego podejść na dwa sposoby, jeden moim zdaniem gorszy, drugi chyba lepszy, chociaż wymagający więcej pól w bazie.

Rozwiązanie pierwsze wygląda następująco: tworzymy tabelę zawierającą pięć (lub cztery, można olać ID i z product_id zrobić index, ale Kohanowy ORM tego nie lubi) pola:

id :          int (PRIMARY, auto_increment)
element_id :  int (INDEX)
field :       varchar [255]
translation : varchar [255]
lang :        enum [pl, en]

Następnie traktujemy to jako worek na tłumaczenia, wrzucając po kolei pola w stylu:

id | element_id |  field  |     translation    |  lang
 1 |          1 |  'name' |  'Udko z kurczaka' | 'pl-pl'
 2 |          1 |  'name' |    'Chicken leg'   | 'en-us'

Jak widać robi to paskudny bałagan i średnio mi się podoba, dlatego odpowiednio je zmodyfikowałem. Idea zostaje praktycznie identyczna tylko zamiast pól fieldtranslation tworzymy tyle pól, ile ma oryginalna tabela i wpisujemy tam wszystkie wartości, np:

id | element_id |       'name'      |         'description'       |  lang
 1 |          1 | 'Udko z kurczaka' |  'Jest chrupiące i pyszne'  | 'pl-pl'
 2 |          1 |   'Chicken leg'   | 'It's crispy and delicious' | 'en-us'

Od razu lepiej, prawda? Brakuje jeszcze jednego: wygodnego wykorzystania tego. Przecież nie będziemy się ciągle partolić z pobieraniem tego typu rzeczy z bazy, ani tym bardziej przeciążać co chwilę ORM-a pierdołami. Dlatego stworzymy sobie prościutki wrapper.

Na początek tworzymy model, dla konwencji nazywając go element_locale[9] (a poprzednią tabelę oczywiście element_locales):

<?php class Model_Element_Locale extends ORM
{
   protected $_belongs_to = array
             (
                 'element' => array('model' => 'element'),
             );
 
}

Następnie przydzielmy go do modelu Element:

<?php class Model_Element extends ORM
{
   protected $_has_many = array
             (
                 'locale' => array('model' => 'element_locale')
             );
 
}

A następnie przeładujmy sobie Element::__get(), żeby było nam łatwiej[10]:

public function __get($column)
{
   if(in_array($column, array('name', 'description')))
   {
      return $this->locale->where('lang','=',i18n::lang())->find()->$column;
   }
 
   return parent::__get($column);
}

Dlaczego nie przeładowujemy przy okazji Element::__set() ? Żeby przypadkiem czegoś nie spieprzyć - wystarczy, że zapomnimy zmienić język podczas zapisywania w dowolnym miejscu kodu - klapa. Poza tym tłumaczenia przy ich edycji powinny być w mniejszym lub większym stopniu od siebie niezależne.

Jeśli komuś bardzo zależy i nie spodziewa się skakania między językami w trakcie pracy skryptu, powyższe może jeszcze skeszować:

protected $_locale;
 
public function l10n()
{
   if( !($this->_locale instanceof ORM))
   {
      $this->_locale = $this->locale->where('lang','=',i18n::lang())->find();
   }
   return $this->_locale;
}
 
public function __get($column)
{
   if(in_array($column, array('name', 'description')))
   {
      return $this->l10n()->$column;
   }
 
   return parent::__get($column);
}

Gdyby się tak uprzeć, można pokusić się jeszcze o automatyczne pobieranie właściwych pól w in_array. Ponieważ $_table_columns w ORM-ie jest typu protected, musimy utworzyć odpowiednią metodę w Element_Locale:

protected static $_non_locale_columns = array('id', 'element_id');
 
public function locale_columns()
{
   $keys = array_keys($this->_table_columns);
   foreach(self::$_non_locale_columns as $column)
   {
      unset($keys[$column]);
   }
   return $keys;
}
A następnie zmodyfikować poprzednią metodę:
public function __get($column)
{
   if(in_array($column, ORM::Factory('element_locale')->locale_columns() ))
   {
      return $this->l10n()->$column;
   }
 
   return parent::__get($column);
}

Ktoś się zaraz zapyta - ale zaraz, dlaczego ORM::Factory, a nie $this->l10n() ? Bo l10n korzysta $this->locale (czyli z __get) i mamy paradoks. Niestety nie mam w tej chwili pomysłu jak to lepiej rozwiązać. Jeśli komuś bardzo zależy, może skorzystać z Database::list_columns() i napisać dzięki temu locale_columns() statycznie, ale moim zdaniem to już będzie za dużo kombinowania. No i można znowu keszować ...no ale bez przesady ;)

Cóż, w tym jakże króciutkim jak na dzisiejsze standardy (wiem, jestem złośliwy, przepraszam) wpisie wspomniałem o wielojęzykowości na stronach internetowych. Oczywiście tematu nie da się wyczerpać i zapewne znajdzie się kilka osób, które będą miały lepszy pomysł jak rozwiązać podane problemy - zapraszam do dyskusji, może się czegoś nowego nauczę ;)

Uff, chyba wracam do pisania na Nerdblogu.

Edit (18.07.2011)

Ostatecznie zdecydowałem się na rozwiązanie, które proponowałem w komentarzach poniżej:

public function __get($column)
{
	try
	{
	    return parent::__get($column);
	}
	catch(Exception $e)
	{
	    return $this->l10n()->$column;
	}
}

Ma ono tylko jedną, "poważną" wadę - lepiej jest używać go, gdy mamy pewność, że wszystkie zależności są w odpowiednim miejscu. W innym wypadku debugowanie jest straszliwie upierdliwe. Niestety Kohana nie zwraca żadnego fajnego Exception, które można filtrować, a parsowanie getMessage() za pomocą wyrażeń regularnych to hack, dlatego pozostaje pilnować kodu, a dopiero później dodać lokalizację ;)

[1] Tak, wiem, dla ciebie to jest łatwe, proste i oczywiste, poza tym ABC ma to już wbudowane, a tak w ogóle to używaj XYZ, bo PHP to syf. Riiiiiight, możesz już sobie iść.

[2] Nie mogę się przemóc i używać potworka językowego 'internacjonalizacja' (od angielskiego internationalisation, w skrócie i18n), przykro mi :(

[3] ...albo nie daj Cthulhu potworka w stylu $_GET['lang'] - za takie coś dostaje się po jajku. Metalowym prętem.

[4] A co, programowanie = używanie mózgu, jak oczekujesz gotowców to idź na jakieś fora albo zawracaj cztery litery cierpliwszym programistom, nie mi

[5] Ostatnio były małe zawirowania względem tego jak ma wyglądać ta metoda, dlatego jeśli korzystasz ze starszej wersji 3.1, koniecznie zaktualizuj.

[6] No bo bez jaj, nie będziemy jednej zmiennej przenosić do config/, prawda?

[7] Zanim zaczniesz się faflunić, że jest to nieefektywne bo wymaga skakania po całym filesystemie trzy razy zamiast jednego, skontaktuj się ze swoją dokumentacją w sprawie cacheowania (http://kohanaframework.org/3.1/guide/api/Kohana#find_file)

[8] Mówiłem o tym linijkę wyżej, ale nie zapomnij włączyć keszowanie na serwerze produkcyjnym. Za każdy request na niekeszowanej stronie Cthulhu zjada kocięta.

[9] Jeśli bardzo nie lubisz podkatalogów to nazwij go sobie elementlocale albo po prostu locale jeśli tłumaczysz tylko jeden rodzaj z bazy, a potem ustaw odpowiednio $_table_name

[10] Jeśli jesteś jednym z tych, którzy "nie lubią magii", bo im się w IDE nie generuje przez to autodopełnianie to idź umrzeć w ogniu.

12 komentarzy

Więcej przypisów niż tekstu, na przyszłość trochę się z nimi rozgranicz, można by większą ich część wrzucić do tekstu właściwego i by się lepiej czytało. Tyle ode mnie PR0bloggera ;)

21.06.2011, 16:43

To i ja pozwolę sobie wtrącić swoje 3 grosze - chętnie poczytam więcej o Kohanie jak coś jeszcze naskrobiesz ;]

21.06.2011, 17:09

@pecet - tak przy 7-mym zaczęło mi coś nie pasować, ale już stwierdziłem, że zostawię. Spodobał mi się ten styl narracji ;)

@Sirkruk - prawdopodobnie będę jeszcze pisał w tym temacie, bo Kohana 3.1 jest moim głównym narzędziem w pracy ;)

21.06.2011, 21:33


webking

Kohana to według mnie najlepszy framework, posiada parę wad (m.in. sprawdzanie danych przy zapisywaniu i tworzeniu rekordów za pomocą ORM).
Najbardziej boli mnie powolny, ale skuteczny rozwój, w planach jest naprawa sporej ilości błędów, lecz mimo to, będziemy musieli troszkę poczekać.
Podobnie do Ciebie, w planach też mam zamiar napisać coś o Kohanie. Kto wie, może się komuś przyda to co nagryzmolę :)

21.06.2011, 22:31

Jeśli szukasz czegoś szybciej się rozwijającego to spróbuj FUEL . Ja potrzebuję raczej stabilnego narzędzia, dlatego aktualny tryb rozwojowy Kohany jest mi na rękę ;)

22.06.2011, 01:44

Teraz pomyślałem, że można jeszcze zrobić to w ten sposób:

public function __get($column)
{
    try
    {
        return parent::__get($column);
    }
    catch(Exception $e)
    {
        return $this->l10n()->$column;
    }
}

Nie mam pojęcia czy zadziała, ale warto by przetestować. Zabiera nam sprawdzanie tabel w bazie i powinno działać szybciej, nie mam jednak pojęcia czy wyjątek zostanie poprawnie przechwycony.

22.06.2011, 22:38


sauveur

Zaraz, moment... Strasznie szybko to wszystko przeczytałem, więc jeżeli źle wnioskuję to można mnie strzelić przez łeb.

Twój sposób na tłumaczenie stron, to korzystanie z tego co skrytykowałeś na samym początku wpisu.
W dodatku chyba są błędy w metodach.

Lecim nie od początku:

public static function i18n($file = NULL, array $data = NULL)
{
    if ((Kohana::find_file('views', I18n::lang().'/'.$file)) !== FALSE)
    {
        return new View(I18n::lang().'/'.$file);
    }
    else
    {
        return new View(self::$default_lang.'/'.$file, $data);
    }
}

Rozumiem, że ta powyższa metoda ma być używana zamiast View::factory? A dlaczego, jak znajdziesz "widok" tłumaczeń to nie przekazujesz do konstruktora zmiennej $data?

Tak po za tym, to nie wydaje mi się to "koszerne". Widoki powinny być w views a nie w i18n.

Co do tego co napisałem na początku, tak czy inaczej i tak będziesz stosował metodę __(). Bo przy ilu widokach Twoje rozwiązanie się sprawdzi? Nie wyobrażam sobie duplikować n razy widoków. Każdorazowa nawet najmniejsza poprawka do jednego widoku, wymusi poprawki w n plikach widoków "tłumaczących".

Zacznijmy od utworzenia pliku application/view.php i wpisania do niego podstawowego szkieletu

Z której wersji kohany korzystasz? Jeżeli 3 i jeżeli nie dostałem alzheimera, klasy tworzy się w classes czyli w Twoim przykładzie `application/classes/view.php'.

Zgadzam się z Tobą, z tym co napisałeś na samym początku, ogólnie przyjęte przez Kohane sposoby tłumaczeń są o kant tyłka roztłuc, mają dużo minusów, sam ciągle obiecuję sobie przysiąść do tego tematu i wynaleźć złoty środek, ale jakoś nie mam czasu.

Co do ORM-a, to przyznam, że nie przeczytałem, postaram się po pracy to ogarnąć.

24.06.2011, 18:55

@sauvyer - masz rację, nie stać mnie na profesjonalną korektę więc zdarzają mi się błędy we wpisach.

Tak po za tym, to nie wydaje mi się to "koszerne". Widoki powinny być w views a nie w i18n.

I są. Zwróć uwagę, że widoki są pobierane z views/JĘZYK/plik, a nie z i18n. W i18n są trzymane stringi, a nie tłumaczenia - pojedyncze fragmenty typu napisy na przyciskach.

Co do tego co napisałem na początku, tak czy inaczej i tak będziesz stosował metodę __(). Bo przy ilu widokach Twoje rozwiązanie się sprawdzi? Nie wyobrażam sobie duplikować n razy widoków. Każdorazowa nawet najmniejsza poprawka do jednego widoku, wymusi poprawki w n plikach widoków "tłumaczących".

Przy dowolnej ilości. Różnica pomiędzy wykorzystaniem __(), a wykorzystaniem dowolnych widoków jest taka, że poprawka w domyślnej wersji językowej przy __() wymusi edycję wszystkich plików z tłumaczeniami (o ile kopiujemy je bezczelnie w całości, a nie stosujemy 'klucze'), podczas gdy modyfikacja jednego widoku nie wymusi edycji o ile zmiana nie będzie tłumaczeniem (co jest dość logiczne i nie da się tego uniknąć ;)).

Co do błędów w metodach to pisałem sobie tylko na kolanie szybkie przykłady, żeby sprawdzić czy zadziała, kod nie był wyciągany z live projektu, dlatego może mieć nieścisłości. Za chwilę poprawię opis i przekazywanie parametrów.

24.06.2011, 19:01

Hmm, trochę zamieszałem się w tłumaczeniu. Jeszcze raz, powoli:

Przy wykorzystaniu __() mamy dwa sposoby robienia tłumaczeń. Albo stosujemy pełne teksty, albo "klucze". Przykłady:

__("To jest sposób wykorzystujący pełne teksty. 
Przy tłumaczeniu szukane jest, czy tego typu treść ma pełne 
tłumaczenie i jeśli tak to wrzucanie jej. Jeśli nie - 
wykorzystywany jest domyślny fragment tesktu")

__("przyklad.klucz")

Druga metoda ma wadę tego typu, że robi nam za duże zagnieżdżenie kluczy. Trudniej się połapać gdzie co jest bez kontekstu (a widok taki kontekst zapewnia, chociażby odpowiednim otaczającym kodem HTML). Pierwsza metoda ma tę wadę, że jeśli zdecyduję się poprawić literówkę (tesktu) to przy 10 tłumaczeniach będę musiał znaleźć 10 plików i przy każdym poprawić tekst bazowy.

Ktoś mógłby stwierdzić, że wykorzystanie kluczy niczym nie różni się od widoków - ależ różni. W kluczach ciężko będzie nam zrobić odmianę wyrazów (np 1 jabłko, 2 jabłka, 5 jabłek) bez konstrukcji typu

echo __("opis.ania_ma") . $x . funkcjaOdmieniajacaWyrazy("jablka") . __("opis.w_kieszeni")

Czy też jakiegokolwiek innego potworka ;) (powyższa konstrukcja psuje przy okazji szyk zdań przy konkretnych tłumaczeniach).

Za wyłapanie błędów wielkie dzięki, jeśli znajdziesz jeszcze jakieś pomyłki, koniecznie daj znać :)

24.06.2011, 19:11


sauveur

@D4rky I są. Zwróć uwagę, że widoki są pobierane z views/JĘZYK/plik, a nie z i18n.

Zwracam Ci honor, źle zobaczyłem. Jest to koszernie, widoki masz w views :)

Ale Twoje rozwiązanie jest moim zdaniem tysiąc razy gorsze. To by było tylko ciągle i nie ustające robienie poprawek w widokach. Zmienisz jeden tag w html-u, dodasz jedną klasę na diva i musisz to robić w kilku plikach. Nie podoba mi się to.

A co, jak ja na przykład piszę aplikację w javascripcie? Mam ~40 klas (plików js) ale serwowanych przez Kohane jako widoki bym mógł używać takich funkcji jak URL::site() i teraz miałbym mieć 40 razy 4 języki? A rozwijanie tych klas? Czekaj idę po żyletki :)

Dwie metody o których mówisz, czyli:

__("Lorem ipsum srutututu");

oraz

__("lorem.ipsum");

zgadzam się, że są do luftu. Takie życie.

Ok, gdybym teraz miał czas i zaczął nad tym myśleć, to spróbowałbym tak:

  • Napisał prosty parser (w sumie to mam napisany, bo tak teraz to robię w kohanie) który lata po wszystkich plikach widoków (a u mnie i controllerach itp) i wyszukuje __("coś"). Rozwiązuje to Twój problem z kluczami itp. Zmieniasz coś w widoku -> odpalasz parser -> otrzymujesz pliki wynikowe w i18n. U mnie po znalezieniu __("") sprawdza czy w pliku tłumaczeń w i18n już taki klucz jest, o takie szmery bajery, nie doskonałe ale na razie musi wystarczyć.
  • Jeżeli chodzi o formatowanie "tekstów" to ja bym się zainteresował http://docs.php.net/messageformatter, nie miałem jeszcze możliwości potestowania, ale na tyle ile to przeglądałem to już się delikatnie podnieciłem.

niech moc będzie z Tobą, miłej nocy :)

24.06.2011, 22:05

To by było tylko ciągle i nie ustające robienie poprawek w widokach. Zmienisz jeden tag w html-u, dodasz jedną klasę na diva i musisz to robić w kilku plikach.

views/main/index:

<div class="cokolwiek">
    <?php View::i18n('main/index/cokolwiek') ?>
</div>

(chociaż wtedy wygodniej by było to zautomatyzować i sprawdzać z jakiego widoku i metody jest wywoływane View::i18n i na podstawie tego pobierać już z odpowiedniego miejsca w strukturze katalogów - wszystko zależy od tego, ile magii i automatyzacji lubisz, ja lubię dużo)

A co, jak ja na przykład piszę aplikację w javascripcie? Mam ~40 klas (plików js) ale serwowanych przez Kohane jako widoki bym mógł używać takich funkcji jak URL::site() i teraz miałbym mieć 40 razy 4 języki? A rozwijanie tych klas? Czekaj idę po żyletki :)

Bym musiał chyba zobaczyć o co ci chodzi na podstawie kodu, bo nie rozumiem problemu. A przynajmniej nie widzę go w takim kontekście, w którym jakiekolwiek tłumaczenie skryptu nie robiłoby problemu.

Zmieniasz coś w widoku -> odpalasz parser -> otrzymujesz pliki wynikowe w i18n. U mnie po znalezieniu __("") sprawdza czy w pliku tłumaczeń w i18n już taki klucz jest, o takie szmery bajery, nie doskonałe ale na razie musi wystarczyć.

Czyli jeśli zmienisz cokolwiek w __("") to musisz szukać ręcznie tego w wygenerowanym pliku i przeklejać tłumaczenie jeszcze raz? Chyba, że mówisz tutaj o wykorzystaniu tego na przyciskach i do krótkich wiadomości, wtedy rozwiązanie jest co najmniej fajne :)

Jeżeli chodzi o formatowanie "tekstów" to ja bym się zainteresował http://docs.php.net/messageformatter, nie miałem jeszcze możliwości potestowania, ale na tyle ile to przeglądałem to już się delikatnie podnieciłem.

Szczerze powiedziawszy nie mam teraz za bardzo czasu, żeby się w to wgryzać, ale to chyba służy bardziej do pilnowania przecinków/kropek i wyciągania danych z tekstu, a nie rozwiązuje problemu odmiany wyrazów etc. Chyba, że nie wiem o co ci dokładnie chodzi, wtedy będę wdzięczny za wyjaśnienia :)

25.06.2011, 14:55


sauveur

Czyli jeśli zmienisz cokolwiek w __("") to musisz szukać ręcznie tego w wygenerowanym pliku i przeklejać tłumaczenie jeszcze raz? Chyba, że mówisz tutaj o wykorzystaniu tego na przyciskach i do krótkich wiadomości, wtedy rozwiązanie jest co najmniej fajne :)

Nie, u mnie wygląda to tak:
- zmieniam coś w tłumaczeniu w widoku/kontrolerze
- odpalam parser, który leci po wszystkich plikach i tworzy tablice tłumaczeń.
- jeżeli pliki i18n nie istnieją, tworzy je
- jeżeli pliki i18n już istnieją, otwiera plik i porównuje jakie nowe elementy doszły, a jakie znikneły. Niech tablica utworzona przez parser nazywa się A a tablica już istniejących tłumaczeń nazywa się B. Jeżeli w tablicy A jest klucz którego nie ma w tablicy B to dodaje go do B. Jeżeli w B jest klucz którego nie ma w A to usuwa go z B bo oznacza to, że jakieś tłumaczenie zostało zmienione. W rezulatcie dostaję informację od parsera które klucze są "nowe". Wtedy ręcznie muszę podać tłumaczenia dla "zmienionych" kluczy (czyli tekstów z widoków). Mówię, to nie jest idealne rozwiązanie, ale działa jako tako :)

Jeżeli chodzi o MessageFormater to jest on bardziej zaawansowany niż tylko proste przecinki i kropki w liczbach i datach :) Czytałem kilka artykułów o tym, ale jak mówię, sam osobiście nie miałem jeszcze okazji się tym pobawić, więc mądrować się nie będę jeszcze.

25.06.2011, 20:03

Podczas komentowania pamiętaj o zachowaniu zasad interpunkcji i ortografii.

Przed dodaniem swojego komentarza przeczytaj dyskusję znajdującą się powyżej. Być może ktoś już napisał to co chcesz powiedzieć lub zostało to wyjaśnione - zadawanie tych samych pytań lub krytykowanie już wyjaśnionych rzeczy jest w złym smaku i niszczy kulturę dyskusji.

Komentarze mają włączony Markdown