2013年12月10日火曜日

Rails多言語対応

今日はRailsで日本語と英語の出し分けを実装してました。さすがRails、多言語対応も安定の簡単さ。グリーみたいにアラビア語対応とかしなくていいから更に楽です。ここ(Rails Internationalization (I18n) API)を見ながらちょいちょいと設定が済みました。 基本的にはI18nというオブジェクトですべてハンドルしております。 I18n.lでlocalize、I18n.tで翻訳をします。

2.0.0p247 :010 > I18n.l Time.now
 => "Tue, 03 Dec 2013 17:19:04 +0000" 
2.0.0p247 :022 > I18n.t "hello"
 => "こんにちわ世界" 
どのLocaleを利用するかはI18n.localeに格納されています。

2.0.0p247 :026 > I18n.locale
 => :jp 
2.0.0p247 :027 > I18n.default_locale
 => :en 
言語リソースはymlで用意する。config/locales/*.ymlというファイルを作るとRailsが自動的に読み込んでくれる。

en:
  hello: "Hello world"
jp:
  hello: "こんにちわ世界"
ちなみに階層化できるので、きれいにまとめましょう。

en:
  greeting:
    hi:
      world: "Hi World"
      ippei: "Hi Ippei"
.(ドット)でつなぎます。

2.0.0p247 :004 > I18n.t("greeting")
 => {:hi=>{:world=>"Hi World", :ippei=>"Hi Ippei"}} 
2.0.0p247 :005 > I18n.t("greeting.hi.world")
 => "Hi World" 
2.0.0p247 :006 > I18n.t("greeting.hi.ippei")
 => "Hi Ippei" 
若干ハマったのが、なぜかjp.ymlという言語リソースファイルを追加したのに読んでくれていない。Railsがconfig/locales/から自動的にロードしてくれるのだが、残念ながらファイルを追加した時には動的に呼んでないらしい。ファイルに新しいKeyをアペンドした時は読んでくれるんだけど。たぶんこのパラメータが起動時に作られるのだと思われる。

2.0.0p247 :001 > I18n.load_path
 => ["/Users/ippei/RubymineProjects/eneberg/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.1/lib/active_support/locale/en.yml", "/Users/ippei/Rubymine.....
また、application.rbからdefault_localeの変更やload_pathの追加をすることもできる。少なくともrails4.0.1では以下のようなComment OutされたエントリーがあるのでComment Inして任意の言語またはディレクトリを追加するだけ。

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de


問題はI18n.localeにどうやって任意の言語を入れてあげるか。つまりどうやって言語を判別するか。 大まかに以下の方法があると思います。組み合わせて使ったりもします。グリーはAccept-Languageだったはず。

  1. ドメインで識別 
    • jp.example.com
    • example.jp
  2. Query Stringで渡す
    • example.com?locale=jp
  3. Pathに追加する
    • example.com/jp
  4. Accept-Languageを見る
    • Accept-Language:en-US,en;q=0.8,ja;q=0.6
  5. GeoIP
  6. ユーザ毎に設定を持たせる

今回はお手軽にQuery StringとAccept-Languageで実装しました。


まずはQuery Stringから。ApplicationController.rbに下記を追加してQuery Stringで渡されたパラメーターをI18n.localeに設定するようにします。

before_action :set_locale
 
def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end
これだけだと画面遷移時にlocaleが引き継がれないので、以下のmethodをoverrideして常にlocaleパラメータを渡すようにします。

def default_url_options(options={})
  { locale: I18n.locale }
end
お次はAccept-Languageの実装です。残念ながらRails自体にはAccept-LanguageをParseする機能はないようなので、http_accept_languageというGemを使いました。 Gemfileに以下を追加します。
gem "http_accept_language", "~> 2.0.0"
そうするとこんな感じで準備したLocaleに対応する言語を返してくれます。
http_accept_language.compatible_language_from(I18n.available_locales)
Query Stringを優先させるので、終わってみるとApplication Controllerはこんな感じになりました。

  before_action :set_locale

  def default_url_options(options={})
    {:locale => I18n.locale}
  end

  def set_locale
    I18n.locale = extract_locale_from_params ||
        extract_locale_from_accept_language_header ||
        I18n.default_locale
  end

  private
  def extract_locale_from_params
    if params[:locale] and I18n.available_locales.index(params[:locale].to_sym)
      params[:locale]
    end
  end

  def extract_locale_from_accept_language_header
    http_accept_language.compatible_language_from(I18n.available_locales)
  end
後はja.ymlとen.ymlにひたすらリソースを追加して、Templateにせこせこと以下の文字を馬鹿みたいに埋め込み続けるだけの簡単なお仕事です。
<%= t('KEY') %>

2013年12月1日日曜日

DNSimpleでHerokuアプリのドメイン登録


Herokuとの連携が簡単そうだったのでDNSimpleというドメインサービスを利用してみたら死ぬほど簡単だったので紹介してみる。DocumentもSimpleにまとまっていて良い(DNSimple Support)。
HeokuのドキュメントもDNSimple推しでまとまってます(SSL Endpoint, Purchasing an SSL Certificate from DNSimple)。
まずはクレジットカードの登録をしてアカウントアクティベートしてみます。ドメインを追加します。フォームに打ち込むだけで取得できるかの確認や、Name Servreへの登録とかもやってくれて数分待っていれば名前が解決できるようになりました。

 
既にとられているドメインだと怒られる。


こんな感じでダッシュボードから登録の状況やALIAS、SOA、NS等の情報が確認できます。




Advance Editorという画面からRecordが登録できるので、サブドメインと一緒にHerokuのアプリへのALIASレコードを作ってやります。ALIASレコードはDNSimpleが追加してたCNAMEの拡張レコードのようなもの。Aレコードがなくてもdomain名からアドレスを解決してくれるものらしい。詳しくはこちら(What is an ALIAS record?, Introducing the ALIAS Record – Naked Domain Aliasing that Works!)。


後は、Herokuのアプリ設定で上で追加したDomainを追加してあげればOK。ここまでで、もう作ったばかりのサブドメインでアプリに接続できるようになりました。



お次はSSLの設定。これもしっかりまとまっています(Using an SSL Certificate with Heroku)。まずはダッシュボードからこんな感じでサブドメインを指定して証明書を購入します。これも一瞬で済みます。サブドメイン一つのみだと$20/year、ワイルドカードだと$100/yearっぽいです。



購入すると発行期間からemailでkeyが送られてくるので、こんな感じで証明書とPrivate Keyを設定。

$ heroku certs:add /tmp/server.crt /tmp/server.key /tmp/bundle.pem 
Resolving trust chain... done
Adding SSL Endpoint to .$oldapp.. done
$oldapp now served by $newapp
Certificate details:
Common Name(s): 
Expires At:.....

注意点としては$newapp.herokussl.comっていうアプリ名がSSL用のエンドポイントとして割り振られるので、ALIASの先をherokuapp.comから変更すること。じゃないと、証明書のドメインと一致しなくて激おこされます。こちらに書いてある通りです(Troubleshooting SSL error for *.heroku.com or *. herokuapp.com)。もともと非SSLのアプリへのALIASにしてあったので、キャッシュの関係で変更後もしばらくは怒られていましたが一晩おいといたらちゃんとアップデートされてhttpsでアクセスできるようになりました。

後は以下のようにConfigでProduction環境のみhttpsを強制するようにすれば終了。

--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -40,7 +40,7 @@Application.configure do
   # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
 
   # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
-  # config.force_ssl = true
+  config.force_ssl = true
という感じで初めての僕でも作業時間半日くらいでドメインを取得してサブドメインでSSL対応のWebアプリを公開するとこまでできてしまいました。もう一度やったら1-2時間で済みそう。ちなみにBatch Server用のAWSのドメイン登録もしたんだけど、こちらはPublic IPが割り振られているのでAレコードを追加するのみでした。