Fork me on GitHub

The WebDevil

Enjoy development

Задавался давно уже вопросом о том, как сделать многоязычный сайт на рельсах. Сначала нехватало знаний, потом времени.
Недавно понадобилось таки реализовать такой сайт. Сразу скажу, получилось.

Первым делом задумался над переводом в шаблонах. Благо, тут вариантов было много и я выбрал simple-localization. С рельсами 2.1 работает версия 3.0. Но она ставится экспортом из svn:

cd vendor/plugins
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 строками:

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

Язык я категорически отказался хранить в сессии – это несерьезно. Так невозможно дать ссылку товарищу и быть уверенным, что он увидит о же что и вы.
В роутах реализовал так:

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

В app/controllers/application.rb добавил парочку методов и фильтр:

class ApplicationController < ActionController::Base
    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 ActionView
  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:

class CreatePages < ActiveRecord::Migration
  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 имеем следующее:

class Page < ActiveRecord::Base
  named_scope :localized, :conditions=>{:lang=>Localization.used.to_s}
end

Так можно оперировать локализоваными страницами как Page.localized.

Вроде бы из интересного все, остальное рутина. Если у кого есть советы как что дополнить/измениь – с радостью выслушаю.

Comments are closed.