scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do
resources :posts
root to: redirect("/%{locale}/posts", status: 302)
end
root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_root
get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: false
Рассмотрим вышеуказанный кусок кода из файла config/routes.rb и построчно изучим что же он делает.
-
К ресурсу постов добавляется параметр локали в адресе:
http://example.com/en/posts
За это отвечает часть:scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do resources :posts end
Стоит отметить, что доступные локали берутся из настроек приложения. -
Корневой путь с указанием локали перенаправляет на локализованную версию постов (это у нас страница по умолчанию)
http://example.com/ru→http://example.com/ru/posts
Данное поведение описывается следующими строками:scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect("/%{locale}/posts", status: 302) end
Здесь используется конструкция%{locale}, которая позволяет применить динамический сегментlocaleизscopeвнутри редиректа. Также очень интересно, как можно передать статус при перенаправлении - об этом практически нигде не написано, а нужно просто указать дополнительный параметрstatus: 302. Того же эффекта можно достичь следующей вариацией:scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect(status: 302) {|params, request| "/#{params[:locale]}/posts"} end
или мы можем опустить неиспользуемые переменные:scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect(status: 302) {|params, _| "/#{params[:locale]}/posts"} end
или даже можем писать вот так, за счет использования динамического сегмента:scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do root to: redirect(status: 302) {|_, _| "/%{locale}/posts"} end
Стоит отметить, что перенаправление без использования блока нагляднее отображается в спискеbin/rake routes. Сравните:# root to: redirect(status: 302) {|_, _| "/%{locale}/posts"} root GET /:locale(.:format) redirect(302) {:locale=>/ru|en/}# root to: redirect("/%{locale}/posts", status: 302) root GET /:locale(.:format) redirect(302, /%{locale}/posts) {:locale=>/ru|en/} -
Корневой путь перенаправляет на локаль по умолчанию
http://example.com/→http://example.com/en/
За это отвечает следующая линия кода:root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_root
Стоит отметить, что здесь обязательно использование параметраas: :redirected_root, поскольку у нас уже объявлен одинroot_pathи второго в Rails 4 быть не может (в отличие от Rails 3) -
Все пути без локали перенаправляются на аналогичный путь с локалью по умолчанию, без зацикливаний:
http://example.com/posts→http://example.com/en/posts
Достигается это за счет самой длинной строчки:get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: false/*path— это так называемый глоббинг. При указании такого параметра в него уходит вся оставшаяся часть строки, включая слэши и GET параметры, а в купе с параметромformat: false— она однозначно обеспечивает сохранение всех символов в адресе, включая сигнатуру.jsonили.htmlв конце.
Параметрconstraintпроверяет, что строка не начинается с параметра локали и только в этом случае происходит перенаправление на локализованную версию. Такой подход позволяет избежать циклических перенаправлений для несуществующих адресов. Также стоит отметить, что в последнем примере используется только GET, ибо POST запросы без указания локали не могут случиться, если только это не намеренный вызов, либо ошибка в коде.
Рассмотрим еще один, альтернативный вариант роутов
scope "/:locale", locale: /#{I18n.available_locales.join("|")}/ do
resources :posts
root to: "main#index"
end
root to: redirect("/#{I18n.default_locale}", status: 302), as: :redirected_root
get "/*path", to: redirect("/#{I18n.default_locale}/%{path}", status: 302), constraints: {path: /(?!(#{I18n.available_locales.join("|")})/).*/}, format: false
Здесь нет редиректа на страницу постов и есть отдельная главная страница.
Хелперы для переключения языков
Кстати, странно, но в Rails 4 поведение при формирование ссылок - меняется, и если бы мы использовали переключалку языка, которая отлично работала в Rails 3, то для всех страниц она бы работала нормально, а вот на главной мы бы получили ссылки /en/main/index и /ru/main/index вместо привычных /en и /ru.
Вот что было в Rails 3:
# app/helpers/application_helper.rb
module ApplicationHelper
def lang_switcher
content_tag(:ul, class: 'lang-switcher clearfix') do
I18n.available_locales.each do |loc|
concat content_tag(:li, (link_to loc, params.merge(locale: loc)), class: (I18n.locale == loc ? "active" : ""))
end
end
end
end
И вот как приходится реализовывать тот же самый хелпер-метод в Rails 4
# app/helpers/application_helper.rb
module ApplicationHelper
def lang_switcher
content_tag(:ul, class: 'lang-switcher clearfix') do
I18n.available_locales.each do |loc|
locale_param = request.path == root_path ? root_path(locale: loc) : params.merge(locale: loc)
concat content_tag(:li, (link_to loc, locale_param), class: (I18n.locale == loc ? "active" : ""))
end
end
end
end
Мне потребовалось некоторое время, чтобы понять как все это работает. Надеюсь данная статья поможет чем-нибудь и вам.