
Rails zawiera wbudowany framework do internacjonalizacji. Aby zacząć, nie potrzeba wiele więcej – interfejs API I18n jest częścią tego środowiska i dobrze radzi sobie z podstawowymi zadaniami: tłumaczeniem ciągów statycznych, formatowaniem dat i liczb oraz obsługą liczby mnogiej w różnych ustawieniach regionalnych.
Jednak „wbudowane” nie oznacza „kompletne”. Gdy tylko Twoje wymagania obejmą zlokalizowane adresy URL, tłumaczenia w JavaScript lub wielojęzyczne SEO, wykraczasz poza to, co Rails oferuje w standardzie. Te luki wymagają przemyślanych decyzji architektonicznych.
Przyjrzyjmy się obu warstwom: temu, czym zajmuje się interfejs API Rails I18n, oraz temu, co trzeba zaimplementować samodzielnie – lub zlecić innym.
Interfejs API I18n udostępnia dwie podstawowe metody: I18n.t do tłumaczenia ciągów znaków oraz I18n.l do lokalizacji dat i liczb. Obie metody odczytują dane z plików YAML lub Ruby znajdujących się w katalogu config/locales/, a Rails ładuje je automatycznie podczas uruchamiania.
W standardowej konfiguracji obejmuje to statyczne ciągi tekstowe interfejsu użytkownika, formatowanie dat i liczb oraz komunikaty walidacyjne ActiveRecord. W przypadku większości projektów, w których występuje tylko jeden język źródłowy i jeden język docelowy, to wystarczy do wydania.
Gdy tylko się oddalisz, luki szybko stają się widoczne.
Rails I18n nie reguluje struktury adresów URL: nie obsługuje zlokalizowanych tras, wykrywania subdomen ani prefiksów ścieżek. Nie posiada mechanizmu tłumaczenia treści przechowywanych w bazie danych, takich jak nazwy produktów czy wpisy na blogu. Pliki JavaScript pozostają całkowicie poza tym procesem. Natomiast wielojęzyczne SEO – obejmujące takie elementy jak tagi hreflang, przetłumaczone slugi oraz mapy witryn dostosowane do poszczególnych języków – wymaga osobnych działań, których framework nie obsługuje.
Zanim zaczniesz budować, musisz wiedzieć, gdzie przebiega granica.
Możesz zacząć od gemu rails-i18n.
Rails dostarcza dane tylko dla ustawień regionalnych języka angielskiego, więc bez nich wyrażenie `I18n.l(Date.today)` nie działa poprawnie w ustawieniach regionalnych innych niż angielskie, a ciągi znaków na poziomie frameworka, takie jak komunikaty walidacyjne Active Record, pozostają w języku angielskim niezależnie od aktualnych ustawień regionalnych.
# Gemfile
gem „rails-i18n”Następnie skonfiguruj domyślne ustawienia aplikacji:
# config/application.rb
config.i18n.default_locale = :en
config.i18n.dostępne_języki = [:en, :fr]
config.i18n.enforce_available_locales = true
Ustawienie `enforce_available_locales = true` powoduje wygenerowanie błędu, jeśli aplikacja próbuje ustawić lokalizację spoza tej listy. Bez tego ustawienia literówka lub nieprawidłowy parametr adresu URL może w środowisku produkcyjnym po cichu spowodować ustawienie nieobsługiwanej lokalizacji.
Chociaż nowoczesna wersja Railsów przeszukuje tylko jeden poziom głębokości, często pomija głęboko zagnieżdżone foldery (takie jak config/locales/views/products/). Dodanie tego wiersza gwarantuje, że wraz z rozwojem aplikacji uwzględnione zostaną wszystkie podkatalogi:
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", „*.{rb,yml}”)]Dzięki temu można podzielić pliki tłumaczeń według domen:
config/locales/
en/
models.yml
widoki.yml
mailers.yml
fr/
modele.yml
widoki.yml
mailers.yml⚠️ YAML interpretuje wartości takie jak „true” i „false” jako wartości logiczne. Jeśli chcesz, aby były traktowane jako zwykły tekst, należy je wyraźnie ująć w cudzysłowy. Nowsze wersje Ruby/YAML traktują obecnie „yes” i „no” jako ciągi znaków, jednak umieszczanie ich w cudzysłowach pozostaje bezpiecznym nawykiem ze względu na kompatybilność:
pl:
odpowiedzi:
tak: „tak”
negatywne: „nie”W ten sposób wykrywa się pewien rodzaj błędu, który ujawnia się tylko wtedy, gdy funkcja tłumaczenia zwraca wartość „true” zamiast „yes”, a widok nie wyświetla niczego lub – co gorsza – wyświetla ciąg znaków „true”.
W widokach t i l to dwa elementy pomocnicze, z których będziesz korzystać na co dzień.
l dostosowuje daty, godziny i liczby zgodnie z bieżącą lokalizacją. Po zainstalowaniu biblioteki rails-i18n dostępne są definicje formatów dla ponad 100 lokalizacji. Bez tej biblioteki wywołanie funkcji l(Date.today) w lokalizacji innej niż angielska zazwyczaj zwraca komunikat „brak tłumaczenia” lub nieformatowaną datę, ponieważ Rails nie dysponuje niezbędnymi wzorcami formatowania dostosowanymi do danej lokalizacji.
<%= l(Date.today) %>
<%= l(Date.today, format: :long) %>t tłumaczy ciągi znaków według klucza. Pełna forma to t("products.index.title"), ale w widokach można użyć skrótu z odroczonym wyszukiwaniem:
<%= t(".title") %>Kropka na początku klucza informuje Railsy, aby rozpoznały go względem bieżącej ścieżki widoku. W pliku app/views/products/index.html.erb wyrażenie t(".title") rozwijane jest automatycznie na t("products.index.title"). Dzięki temu klucze pozostają krótkie, a spójna konwencja nazewnictwa jest przestrzegana bez dodatkowego wysiłku.
For interpolation, use %{variable} in your YAML and pass the value as a keyword argument:
en:
welcome: "Hello, %{name}"<%= t(".welcome", name: current_user.name) %>Jeśli tłumaczenie zawiera kod HTML, któremu ufasz, dodaj do nazwy klucza rozszerzenie _html. Rails automatycznie oznaczy go jako bezpieczny, bez konieczności wywoływania metody html_safe.
en:
notice_html: "Please <strong>confirm</strong> your email."Leniwe wyszukiwanie działa tylko wtedy, gdy Rails może wywnioskować ścieżkę widoku, co oznacza, że nie działa w zadaniach wykonywanych w tle ani w kontrolerach API. W tych przypadkach należy za każdym razem używać pełnego klucza.
Zanim zaczniesz pisać kod do wykrywania ustawień regionalnych, wybierz strategię. Istnieje pięć głównych podejść, a wybór odpowiedniego zależy od struktury Twojej aplikacji oraz wymagań dotyczących SEO.
Prefiks ścieżki, np. /fr/products, jest najczęściej wybieranym rozwiązaniem w aplikacjach Railsowych przeznaczonych dla użytkowników zewnętrznych. Dzięki temu w każdym adresie URL wyraźnie wskazane jest ustawienie regionalne, co pozwala wyszukiwarkom indeksować strony osobno dla poszczególnych języków.
Strategie oparte na subdomenach i domenach najwyższego poziomu sprawdzają się, gdy zależy nam na silniejszej tożsamości regionalnej, ale wiążą się one z dodatkowymi nakładami związanymi z konfiguracją DNS i certyfikatami SSL.
Parametry adresów URL, takie jak /products?locale=fr, sprawdzają się w przypadku narzędzi wewnętrznych, gdzie pozycjonowanie stron (SEO) nie ma znaczenia.
Ustawienia zapisane w bazie danych działają w przypadku zalogowanych aplikacji, w których ustawienia regionalne są dostosowywane do użytkownika, a nie do adresu URL.
Niezależnie od tego, którą strategię wybierzesz, istnieje jeden błąd wdrożeniowy, który powoduje subtelne błędy w środowisku produkcyjnym: bezpośrednie użycie `I18n.locale =`.
# Don't do this
before_action { I18n.locale = params[:locale] }I18n.locale = zapisuje do Thread.current.
Jeśli korzystasz z serwera internetowego Puma, wykorzystuje on te same wątki dla różnych żądań. Jeśli w żądaniu nie podano wyraźnie ustawień regionalnych, przejmuje ono ustawienia z poprzedniego żądania.
W środowisku lokalnym, w którym działa tylko jeden wątek, problem ten nigdy nie występuje. W środowisku produkcyjnym powoduje to sporadyczne wyświetlanie odpowiedzi w niewłaściwym języku, które trudno odtworzyć, a jeszcze trudniej zlokalizować.
Zamiast tego użyj funkcji `I18n.with_locale` w akcji typu `around_action`:
around_action :switch_localed
ef switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
koniecFunkcja `with_locale` przywraca poprzednie ustawienia regionalne po zakończeniu bloku, niezależnie od tego, jak zakończy się żądanie.
Zdefiniuj trasy w katalogu /:locale i nadpisz ustawienia default_url_options, aby Rails automatycznie dodawał bieżącą lokalizację na początku każdego adresu URL generowanego przez pomocniki:
# config/routes.rb
scope "/:locale" do
resources :products
root "home#index"
end
# app/controllers/application_controller.rb
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale]
# Fallback to default if the param is missing or unsupported
valid_locale = I18n.available_locales.map(&:to_s).include?(locale) ? locale : I18n.default_locale
I18n.with_locale(valid_locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
Po ustawieniu parametru `default_url_options` ścieżka `products_path` jest automatycznie renderowana jako `/fr/products`, gdy bieżącym ustawieniem regionalnym jest :fr.
Jeśli chcesz, aby domyślna lokalizacja pomijała przedrostek, ustaw segment jako opcjonalny, używając zakresu „(:locale)”. Dzięki temu adres /products będzie wyświetlał stronę w języku angielskim, a /fr/products – w języku francuskim, ale spowoduje to niejasność. Ścieżka taka jak /about może odpowiadać albo brakującej lokalizacji, albo kontrolerowi o nazwie „about”, w zależności od kolejności tras.
Aby wykryć subdomenę, przed dokonaniem jakichkolwiek ustawień należy odczytać wartość z `request.subdomains.first` i porównać ją z listą `available_locales`:
def wyodrębnij_język_z_poddomeny
subdomain = request.subdomains.first
zwróć nil jeśli subdomain.blank? || subdomain == "www"
subdomain jeśli I18n.available_locales.map(&:to_s).include?(subdomain)
end
Sprawdzenie „www” powoduje, że nie jest on traktowany jako ustawienie regionalne. Na serwerze localhost funkcja request.subdomains zwraca pustą tablicę, więc wykrywanie subdomen nie działa w środowisku deweloperskim, chyba że używasz serwera Pow lub dodasz wpisy do pliku /etc/hosts.
Jedną rzecz należy skonfigurować osobno: opcja `default_url_options` zdefiniowana w klasie `ApplicationController` nie jest przenoszona do modułów wysyłających wiadomości e-mail ani zadań wykonywanych w tle. Należy ją ustawić wyraźnie dla tych kontekstów:
Rails.application.routes.default_url_options = { host: "example.com", locale: :en }Zlokalizowane trasy zapewniają strukturę adresów URL, jednak po tej konfiguracji tagi hreflang, przetłumaczone slugi oraz wielojęzyczne mapy witryny nadal wymagają ręcznej obsługi.
Integracja z serwerem proxy odwróconym Weglot automatycznie zajmuje się tą warstwą, generując tagi hreflang i adresy URL dostosowane do konkretnego języka bez konieczności dodatkowej konfiguracji.
Tworzenie liczby mnogiej w języku angielskim jest proste. Jedna forma dla liczby pojedynczej, jedna dla wszystkich pozostałych przypadków.
en:
messages:
one: "%{count} message"
other: "%{count} messages"
Większość języków nie jest aż tak prosta.
Na przykład w języku bułgarskim niektóre słowa w rodzaju męskim mają regularną liczbę mnogą oraz specjalną „liczbę mnogą liczbową”. Tak więc słowo ден (dzień) w normalnym użyciu oznacza дни (wiele dni), ale w przypadku liczenia brzmi два дена (dwa dni).
bg:
cities:
one: "%{count} ден"
few: "%{count} дена"
many: "%{count} дни"
Bez modułu rails-i18n framework Rails nie ma wiedzy na temat tych reguł. Twoje bułgarskie tłumaczenia będą generować błędy lub przy każdym liczeniu będą przechodzić na alternatywną formę.
Gem obsługuje logikę tworzenia liczby mnogiej dla ponad 100 ustawień regionalnych, dzięki czemu nazwy wyświetlają się poprawnie w języku bułgarskim, rosyjskim, arabskim, polskim i każdym innym języku, w którym zasady tworzenia liczby mnogiej odbiegają od angielskich.
Rozwiązania awaryjne to osobna kwestia i domyślnie są wyłączone. Bez nich każdy brakujący klucz tłumaczenia powoduje wyświetlenie komunikatu o braku tłumaczenia w środowisku produkcyjnym. To właśnie tego typu sytuacje trafiają do użytkowników i pojawiają się na zrzutach ekranu.
Włącz opcje awaryjne i zdefiniuj domyślne miejsce docelowe w pliku config/application.rb:
config.i18n.fallbacks = [I18n.default_locale]Gdy ustawiono opcję `fallbacks = true`, brakujący klucz w bieżącej lokalizacji jest zastępowany wartością z `default_locale`. W przypadku wariantów regionalnych można zdefiniować łańcuchy w sposób jawny:
config.i18n.fallbacks = { "fr-CA": :fr, "en-GB": :en }Zadbaj o to na samym początku. Wprowadzenie mechanizmu awaryjnego do aplikacji, która jest już częściowo przetłumaczona i działa w środowisku produkcyjnym, jest trudniejsze niż skonfigurowanie go od samego początku.
JavaScript nie ma dostępu do potoku I18n w Railsach.
Nie ma tu żadnego modułu pomocniczego typu t, modułu ładującego YAML ani wbudowanego pomostu między plikami tłumaczeń a kontrolerami Stimulus. Tłumaczenia trzeba przekazywać jawnie po stronie serwera.
Najprostszym sposobem korzystania z biblioteki Stimulus jest użycie interfejsu API wartości. Zdefiniuj tłumaczenie jako wartość w kontrolerze, ustaw je w kodzie HTML i odczytaj w JavaScript:
# app/views/products/index.html.erb
<div data-controller="notification"
data-notification-message-value="<%= t('.success_message') %>">
Wartość ta jest renderowana po stronie serwera przy użyciu standardowego pomocnika `t`, dzięki czemu automatycznie dostosowuje się do bieżących ustawień regionalnych. Po stronie JavaScript należy zadeklarować ją jako wartość statyczną i odczytywać za pomocą interfejsu API wartości:
// app/javascript/controllers/notification_controller.js
export default class extends Controller {
static values = { message: String }
show() {
alert(this.messageValue)
}
}
Każde tłumaczenie jest jednoznaczne i ograniczone do kontrolera, który go potrzebuje. Żadne dane nie przedostają się do zakresu globalnego.
Jeśli kontroler potrzebuje kilku tłumaczeń jednocześnie, zebranie ich w postaci atrybutu danych JSON jest bardziej przejrzystym rozwiązaniem niż dodawanie osobnych definicji wartości dla każdego ciągu znaków:
<div data-controller="cart"
data-cart-i18n-value="<%= { add: t('.add'), remove: t('.remove') }.to_json %>">
W JavaScript należy zadeklarować i18n jako obiekt i uzyskiwać bezpośredni dostęp do poszczególnych kluczy:
static values = { i18n: Object }
add() {
console.log(this.i18nValue.add)
}
W przypadku aplikacji, w których JavaScript wymaga szerokiego dostępu do tłumaczeń w wielu kontrolerach, gem i18n-js eksportuje pliki YAML do obiektu JavaScript, z którego można pobierać dane za pomocą wyrażenia I18n.t("key").
To działa, ale wymaga dodatkowego etapu kompilacji i powoduje wysłanie pełnego zestawu tłumaczeń do klienta. Należy z tego korzystać, gdy pierwsze dwa schematy stają się powtarzalne, a nie jako domyślne rozwiązanie.
W przypadku korzystania z Turbo Frames, jeśli adres URL źródła ramki Turbo Frame nie zawiera przedrostka lokalizacji, żądanie zazwyczaj zakończy się niepowodzeniem z błędem routingu (404), ponieważ nie będzie pasowało do wzorca zakresu „/:locale”. W źródłach ramek należy zawsze stosować pomocniki ścieżek uwzględniające lokalizację.
To nie jest błąd Turbo. Jest to raczej przeoczenie związane z routingiem, a akcja `around_action` z sekcji wykrywania ustawień regionalnych zapobiega temu, o ile każdy adres URL w ramce korzysta z pomocnika ścieżki uwzględniającego ustawienia regionalne.
Proxy odwrotne Weglot działa na zupełnie innej zasadzie. Przetwarza ono wyrenderowany model DOM po załadowaniu strony, dzięki czemu treści wyrenderowane przez Stimulus oraz dynamicznie wstawiane ciągi znaków są obsługiwane bez konieczności stosowania jakichkolwiek dodatkowych konfiguracji.
Istnieją trzy kategorie, które całkowicie wykraczają poza zakres obsługi funkcji Rails I18n:
Weglot rozwiązuje wszystkie te kwestie na poziomie warstwy wyjściowej, a nie warstwy kodu:
Warto jednak pamiętać o kilku ograniczeniach, zanim przystąpisz do oceny:
Każda implementacja i18n w Railsach sprowadza się do tych samych decyzji podejmowanych mniej więcej w tej samej kolejności.
Najpierw należy wybrać strategię adresów URL przed rozpoczęciem pisania kodu do wykrywania ustawień regionalnych. Segmenty ścieżki sprawdzają się w większości aplikacji przeznaczonych dla użytkowników zewnętrznych. Poddomeny mają sens, gdy istotna jest tożsamość regionalna. Preferencje zapisane w bazie danych nadają się do aplikacji wymagających uwierzytelnienia, w których ustawienia regionalne są przypisane do użytkownika, a nie do adresu URL.
Po drugie, należy zdecydować, w jaki sposób JavaScript ma pobierać tłumaczenia. Interfejs API wartości Stimulus obejmuje większość przypadków. Atrybuty JSON sprawdzają się, gdy kontroler potrzebuje kilku ciągów znaków jednocześnie.
Po trzecie, zdecyduj, co chcesz tworzyć samodzielnie. Rails dobrze radzi sobie ze statycznymi ciągami znaków. Warstwa SEO, proces tłumaczenia oraz zawartość bazy danych wymagają osobnych decyzji. Możesz zająć się każdym z tych elementów samodzielnie lub powierzyć obsługę warstwy wyjściowej narzędziu takiemu jak Weglot poświęcić czas programistów na samą aplikację.
Zrezygnuj całkowicie z warstwy wyjściowej. Wypróbuj Weglot przez 14 dni i zyskaj dostęp do wielojęzycznego SEO/GEO, przetłumaczonych adresów URL oraz automatycznego wykrywania treści bez konieczności ingerowania w kod i18n.
Najlepszym sposobem, aby zrozumieć potęgę Weglot wypróbowanie go samodzielnie. Wypróbuj go za darmo i bez żadnych zobowiązań.
Jeśli nie jesteś jeszcze gotowy, aby połączyć swoją stronę internetową, w panelu administracyjnym dostępna jest strona demonstracyjna.