Задавался давно уже вопросом о том, как сделать многоязычный сайт на рельсах. Сначала нехватало знаний, потом времени.
Недавно понадобилось таки реализовать такой сайт. Сразу скажу, получилось.
Первым делом задумался над переводом в шаблонах. Благо, тут вариантов было много и я выбрал simple-localization. С рельсами 2.1 работает версия 3.0. Но она ставится экспортом из svn:
svn co http://svn.arkanis.de/projects/rails_plugins/simple_localization/branches/3.0 simple_localization
Теперь можно вставлять конструкции l(:domain, :key), которые из файла перевода из раздела app: будут брать домен и ключ, указанные в методе. Кстати, я вынес файлы перевода в директорию translations в корень проекта.
Также в перевод я добавил массив с датами в родительном падеже (:declinable_monthnames).
Подключается плагин в config/initializers/simple_localization.rb строками:
class Date
silence_warnings do
DECLINABLE_MONTHNAMES = ArkanisDevelopment::SimpleLocalization::ProxyObject.new :dates, :declinable_monthnames do |localized_data|
[nil] + localized_data
end
end
end
Язык я категорически отказался хранить в сессии – это несерьезно. Так невозможно дать ссылку товарищу и быть уверенным, что он увидит о же что и вы.
В роутах реализовал так:
map.with_options :path_prefix => '/:lang' do |lang|
lang.connect 'news', :controller=>"news", :action=>"index"
lang.connect 'news/:id', :controller=>"news", :action=>"show"
lang.connect '', :controller=>"pages", :action=>"index"
lang.connect 'feedback', :controller=>"pages", :action=>"feedback"
lang.connect '*link', :controller=>"pages", :action=>"show"
end
map.root :controller => "pages", :action=>"index"
end
В app/controllers/application.rb добавил парочку методов и фильтр:
before_filter :localize
...
def localize
if params && params[:lang] && Localization.loaded_languages.include?(params[:lang].to_sym)
Localization.use params[:lang].to_sym
end
end
#it can accept only URL_FOR :(
def localized_redirect(url)
url = '' if url=='/'
redirect_to "/#{Localization.used.to_s}/#{url}"
end
end
Тут я задумался как работать с урлами. Ведь они же будут на корень ссылаться.
Так в config/initializers/simple_localization.rb был добавлен еще кусок кода, в котором переопределялись основные хелперы:
module Helpers
module UrlHelper
def url_for(options = {})
case options
when Hash
show_path = options[:host].nil? ? true : false
options = { :only_path => show_path }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
url = @controller.send(:url_for, options)
when String
escape = true
url = options
when NilClass
url = @controller.send(:url_for, nil)
else
escape = false
url = polymorphic_path(options)
end
url = escape ? escape_once(url) : url
if url =~ /\/.+/ and url !~ /^\/(#{Localization.loaded_languages.join('|')})(\/.+)?$/
url = "/#{Localization.used.to_s}#{url}"
end
url
end
def link_to(name, options = {}, html_options = nil)
url = case options
when String
self.url_for options
when :back
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
self.url_for(options)
end
if html_options
html_options = html_options.stringify_keys
href = html_options['href']
convert_options_to_javascript!(html_options, url)
tag_options = tag_options(html_options)
else
tag_options = nil
end
href_attr = "href=\"#{url}\"" unless href
"<a #{href_attr}#{tag_options}>#{name || url}</a>"
end
end
end
end
Я знаю что это жутко некрасиво, но я не знаю как изменить в методе всего одну строку. Можно было создать свой хелпер, но хотелось переопределить существующие, чтобы все было по возможности прозрачно.
Теперь дело дошло и до хранения локализованого контента в БД. Вот как у меня устроена таблица Pages:
def self.up
create_table :pages, :force=>true do |t|
t.string :lang, :title, :link, :null=>false
t.text :body
t.boolean :textilize, :default=>1, :null=>false
t.timestamps
end
add_index :pages, [:link, :lang], :unique=>true
add_index :pages, [:link]
end
def self.down
drop_table :pages
end
end
а в app/models/page.rb имеем следующее:
named_scope :localized, :conditions=>{:lang=>Localization.used.to_s}
end
Так можно оперировать локализоваными страницами как Page.localized.
Вроде бы из интересного все, остальное рутина. Если у кого есть советы как что дополнить/измениь – с радостью выслушаю.