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
Мне потребовалось некоторое время, чтобы понять как все это работает. Надеюсь данная статья поможет чем-нибудь и вам.