Multilingual site on Rails
Задавался давно уже вопросом о том, как сделать многоязычный сайт на рельсах. Сначала нехватало знаний, потом времени.
Недавно понадобилось таки реализовать такой сайт. Сразу скажу, получилось.
Первым делом задумался над переводом в шаблонах. Благо, тут вариантов было много и я выбрал simple-localization. С рельсами 2.1 работает версия 3.0. Но она ставится экспортом из svn:
[cc lang="bash"]
cd vendor/plugins
svn co http://svn.arkanis.de/projects/rails_plugins/simple_localization/branches/3.0 simple_localization
[/cc]
Теперь можно вставлять конструкции l(:domain, :key), которые из файла перевода из раздела app: будут брать домен и ключ, указанные в методе. Кстати, я вынес файлы перевода в директорию translations в корень проекта.
Также в перевод я добавил массив с датами в родительном падеже (:declinable_monthnames).
Подключается плагин в config/initializers/simple_localization.rb строками:
[cc lang="ruby"]
SimpleLocalization.init :languages => [:ru, :en, :uk], :lang_file_dir => "#{RAILS_ROOT}/translations"
class Date
silence_warnings do
DECLINABLE_MONTHNAMES = ArkanisDevelopment::SimpleLocalization::ProxyObject.new :dates, :declinable_monthnames do |localized_data|
[nil] + localized_data
end
end
end
[/cc]
Язык я категорически отказался хранить в сессии - это несерьезно. Так невозможно дать ссылку товарищу и быть уверенным, что он увидит о же что и вы.
В роутах реализовал так:
[cc lang="ruby"]
ActionController::Routing::Routes.draw do |map|
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
[/cc]
В app/controllers/application.rb добавил парочку методов и фильтр:
[cc lang="ruby"]
class ApplicationController 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
"#{name || url}"
end
end
end
end
[/cc]
Я знаю что это жутко некрасиво, но я не знаю как изменить в методе всего одну строку. Можно было создать свой хелпер, но хотелось переопределить существующие, чтобы все было по возможности прозрачно.
Теперь дело дошло и до хранения локализованого контента в БД. Вот как у меня устроена таблица Pages:
[cc lang="ruby"]
class CreatePages 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
[/cc]
а в app/models/page.rb имеем следующее:
[cc lang="ruby"]
class Page {:lang=>Localization.used.to_s}
end
[/cc]
Так можно оперировать локализоваными страницами как Page.localized.
Вроде бы из интересного все, остальное рутина. Если у кого есть советы как что дополнить/измениь - с радостью выслушаю.