Rails3 & Globalize2

Opublikowane przez Jarosław Zabiełło Fri, 02 Jul 2010 12:42:00 GMT

Standardowa metoda umiędzynarodowienia aplikacji webowej oparta na plikach YAML, czy bibliotece Gettext, dobrze się sprawdza w przypadku tłumaczenia interfejsu aplikacji, ale już niekoniecznie w sytuacji, kiedy teksty trzymane są w bazie danych. Standardowo i18n w Rails używa plików YAML (choć można użyć Gettext lub zdefiniować zupełnie własną obsługę). Aby tłumaczyć dane trzymane w bazie, trzeba sięgnąć po odpowiedni plugin.

Jednym z najlepszych pluginów (opisywałem go w książce), jest Globalize. Niestety, Globalize nie działa z nowym Rails 3. Istnieje co prawda nowy, przebudowany od podstaw Globalize2, ale (w chwili pisania tego tekstu), nawet sam autor nie jest pewny czy działa z Rails 3. Okazuje się, że można zmusić Globalize2 do pracy z Rails 3 i to całkiem prosto.

Należy pamiętać, aby aktualnej wersji Globalize2 nie instalować z generatora, tj. za pomocą komendy:

rails g plugin git://github.com/joshmh/globalize2

To nie zadziała, gdyż plugin nie jest jeszcze dostosowany do nowego API wymaganego dla pluginów w Rails 3. Zamiast tego, należy po prostu pobrać pliki i wrzucić je do katalogu vendors/plugins/:

cd vendors/plugin
git clone git://github.com/joshmh/globalize2

Załóżmy że mamy model Post z polami title, text, created_at i updated_at. Nie chcemy aby wszystkie były tłumaczone, lecz tylko pierwsze dwa. W tym celu należy dokonać dwóch modyfikacji w kodzie.

Po pierwsze, za pomocą makra translates należy w modelu wskazać które pola będą tłumaczone.

# app/models/post.rb
class Post < ActiveRecord::Base
  translates :title, :text
end

Po drugie, należy zmodyfikować plik migracji tak aby tworzył dodatkową tabelę gdzie będą trzymane tłumaczenia. Każdy model podlegający tłumaczeniu musi mieć taką stworzyszoną tabelę. Należy do niej przenieść definicje kolumn które będą tłumaczone tak jak opisano poniżej w komentarzu:

class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts, :force => true do |t|
      # pola podlegające tłumaczeniu muszą zostać wyniesione 
      # do poniższej tabeli post_translations
      t.timestamps
    end

    # W Globalize2 każdy model podlegający tłumaczeniu posiada
    # stowarzyszoną tabelę o nazwie z suffiksem _translations
    create_table :post_translations, :force => true do |t|
      # dwa pola używane przez Globalize2
      t.string     :locale
      t.references :post
      # oraz pola podlegające tłumaczeniu (przeniesione z tabeli posts)
      t.string     :title
      t.text       :text
    end
    # opcjonalny indeks dla przyśpieszenia operacji czytania
    add_index :post_translations, :locale
    add_index :post_translations, [:locale, :post_id], :unique => true
  end

  def self.down
    drop_table :posts
    drop_table :post_translations
  end
end

Wystarczy teraz odpalić migrację i gotowe. Aplikacja jest gotowa do tłumaczenia.

Przykładowe dodawanie i odczytywanie różnych wersji językowych:

# Wersja angielska jest domyślna
puts I18n.locale # => :en
post = Post.new
post.title = "Hello world!" 
post.text = "Globalize2 rulez" 
# można dodawać kilka tłumaczeń naraz
# trzeba tylko przełączyć locale
I18n.locale = :pl
post.title = "Witaj świecie!" 
post.text = "Globalize2 rządzi" 
post.save!

post = Post.first
puts post.title # => Witaj świecie!
I18n.locale = :en
puts post.title # => Hello world!

Zobacz też:

Aktualizacja:

Jeśli nie będzie ci działać metoda “translates” w modelu, to skorzystaj z poprawki http://github.com/joshmh/globalize2/issues#issue/35. Między Rails 3.0 beta4 a Rails 3.0 RC wywalono metodę class_name z ActiveRecord::Base. Zlokalizowałem punkt w Globalize gdzie następuje błąd i po prostu przywróciłem brakującą implementację dla tej usuniętej metody. Aby było prościej, stworzylem fork na githubie.

cd vendors/plugins
git clone git://github.com/hipertracker/globalize2.git

Uwaga, niedawno, na githubie pojawiła się kolejna wersja Globalize3 (http://github.com/svenfuchs/globalize3). Jest zgodna z Rails od wersji 3.0RC w górę. I jest zarządzane przez Bundlera, czyli wystarczy do Gemfile dodać linijkę:

gem 'globalize3'

I oczywiście zaktualizować projekt

bundle update

Tagi , , , , , , ,  | 9 comments

Comments

  1. Avatar szymon powiedział about 4 hours later:

    Super, czyli kolejna niedorobiona wersja czegoś w Railsach, hack tu, hack tam, a potem reszta zespołu się nie połapie, upgrade do nowej wersji się nie uda i w ogóle to jedna wielka łata na łacie zamiast porządnego programowania.

  2. Avatar Jarosław Zabiełło powiedział about 6 hours later:

    Szymon, nie przesadzaj. Jaka tu niedoróbka? Django też nie ma wbudowanej obsługi i18n dla danych w bazie. Zrzucono to, podobnie jak w Rails, na zewnętrzne pluginy. I czasami pluginy nie są jeszcze gotowe do pracy z najnowszą wersją frameworka. Globalize to zasadniczo standard dla tłumaczeń tekstów w bazie dla Rails. Poza tym Globalize2 jest prostszy od Globalize1, i dobrze.

  3. Avatar szymon powiedział 2 days later:

    Cześć, po kolei:

    1. Nie interesuje mnie Django.

    2. Jeśli do uruchomienia tego “pluginu” trzeba robić takie obejścia, to nie jest to “plugin” ale niedoróbka.

  4. Avatar Jarosław Zabiełło powiedział 2 days later:

    Szymon, coś ci się poplątało.

    Po pierwsze, nie można obwiniać Rails o to że jakiś zewnętrzny plugin, który nie jest dystrybuowany standardowo z frameworkiem, ma jakiś problemy.

    Po drugie, Globalize działa od dawna dobrze ze stabilną wersją Rails (2.x).

    Po trzecie, nie ma jeszcze finalnej, stabilnej, wersji Rails 3, więc nie można się czepiać o to, że jakieś zewnętrzne pluginy nie są w pełni dostosowane do Rails 3. Aktualnie trwają prace na dostosowaniem starych pluginów do nowego API Rails 3, i ich stan jest monitorowany na stronie http://www.railsplugins.org/. Globalize2 jest przepisanym od nowa kodem, jest w trakcie rozwoju (tak jak samo Rails 3) więc to jest sytuacja przejściowa. W blogu podałem sposób uruchomienia go już teraz, mimo że to nie są jeszcze wersje finalne.

    Każda wersja rozwojowa kodu z definicji jest niestabilna, ale to nie znaczy, że jest to od razu jakąś ”niedoróbką”. Tu nie mamy jeszcze do czynienia z końcowym, finalnym produktem. Czepiasz się bez sensu.

    Dodam jeszcze, że właściwie to to, co opisałem to żaden hack. Jedynie instalacja jest dokonana przez zwykłe skopiowanie kodu Globalize2 do vendo/plugins. Oraz nie używam wbudowanej metody .create_translation_table!, tylko zrobiłem to ręcznie w pliku migracji. Jest to bardziej explicite i daje mi większą kontrolę nad dodaniem indeksów tak, jak mi się bardziej podoba. Reszta się nie zmienia. Jest dokładnie tak jak opisuje dokumentacja.

  5. Avatar maciek powiedział 27 days later:

    które globalize2 jest właściwe 1 http://github.com/joshmh/globalize2 (do tego podajesz linki), czy 2 http://github.com/jodosha/globalize2 (to klonujesz z githuba)

    i druga sprawa, czy nie masz problemów z tłumaczeniami dla błędów np. walidacji activerecord, mam taki wpis w pl.yml activerecord.errors.messages.blank: “to pole nie może być puste” i kiedy próbuję to wyświetlić (wg railsowego scaffold) @post.errors.full_messages.each do |msg| msg end zwraca mi dziwne hashe, albo jeśli czegoś brakuje w pliku tłumaczenia “odd number of arguments for Hash”

    używam: ruby 1.9.2 rails 3.0RC globalize2 to drugie

    dzięki za pomoc

  6. Avatar Jarosław Zabiełło powiedział 27 days later:

    Maciek, podałem dobry link. Ten pierwszy jest poprawny. Drugi to najwyraźniej jakiś porzucony kod, zobacz jak stare są daty ostatnich zmian.

    Scaffoldingu nie używam, ale sprawdź czy na pewno masz w pliku (RAILS_ROOT/config/locales/pl.yml) na początku “pl”, czyli:

    pl:
      errors:
        messages:
          blank: "to pole nie może być puste" 
    

    Poprawiłem błędny link ze środka tekstu.

  7. Avatar maciek powiedział 27 days later:

    faktycznie takie rozwiązanie działa moje wyglądało tak pl: activerecord: errors: messages: blank: “tu komunikat” (czyli pod pl miałem activerecord) wg tego http://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/pl.yml dzięki

    ps link do pomocy formatowania nie działa

  8. Avatar Jarosław Zabiełło powiedział about 1 month later:

    Brakowało pliku do obsługi YAML’a. Dodane. Zaktualizowałem tekst o łatę pozwalającą na używanie Globalize2 w Rails 3.0 RC (dla wygody stworzyłem swój fork Globalize2).

  9. Avatar Jarosław Zabiełło powiedział about 1 month later:

    Jest już Globalize3 dla Rails 3 (od wersji 3.0RC wzwyż)

(leave url/email »)

   Pomoc języka formatowania Obejrzyj komentarz