鼬~~哩賀,我是寫程式的山姆老弟,前幾天跟大家一起做了很多 Rails 前端的實驗,今天回歸一下 RailsGuide 還沒看的內容,今天要看的是 Rails 的 Internationalization
API,縮寫為 i18n
API,這個縮寫邏輯跟 kubernetes
= k8s
一樣,就是把太長的英文字,中間有幾個字母就用數字代替。
我其實很不熟 i18n,在我的 side project 中,也沒有必須要做多國語言的硬需求,所以也沒花時間去研究,今天就來看看,夠夠~
在 Gemfile 新增 rails-i18n
gem,並執行 $ bundle install
# Gemfile
gem 'rails-i18n'
在 application.rb
或其他環境設定檔(config/environments/*.rb
),可以設定可用語言(avaiable_locales) 和 預設語言(default_locale)
# config/application.rb
...
module TestI18n
class Application < Rails::Application
config.load_defaults 7.0
...
config.i18n.available_locales = [:en, :zh]
config.i18n.default_locale = :en
end
end
我們設定可用語言是英文跟中文,並且預設語言是英文
手動新增 config/locales/zh.yml
,新增一個中文的對照表
# config/locales/zh.yml
zh:
hello: '哈囉握的'
可以打開同一資料夾的 config/locales/en.yml
比較一下
# config/locales/zh.yml
en:
hello: 'Hello world'
會使用多國語言的情境,大概就是
在 app/controllers/application_controller.rb
根據 API 參數判斷語言
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
end
這樣使用者只要在 127.0.0.1:3000?locale=zh
,就能看到中文內容囉
但我就問,使用者怎麼會知道要這樣加!? 難道要開發者每個連結都要記得加上這個 locale 的參數嗎?所以 Rails 提供一個很方便的功能,請繼續看下去。
在 app/controllers/application_controller.rb
覆寫(override) default_url_options
這個 method,所以現在的 ApplicationController 長下面這樣
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
end
覆寫 default_url_options 的話,只要透過 link_to
, url_for
, xxx_path
的連結,都會自動帶上 locale 的參數,這樣開發者就不需要在每個頁面都手動加上 locale 的參數,就像下面這樣
如果你原本的頁面就是沒有帶 locale 參數的話,Rails 將會使用當初設定的 config.i18n.default_locale
當作預設的 url 參數
再假如,如果你已經引導使用者到中文頁面了,那麼這時候產生的連結,會自動帶上中文的 locale,這樣的設定下,對開發者來說是挺方便的,就像下面這樣~
又或者是你覺得用參數醜醜的,想要直接變成網址的一部分,變成像是 127.0.0.1:3000/zh/posts
這樣的形式
# config/routes.rb
Rails.application.routes.draw do
scope "(:locale)" do
resources :posts
end
root 'home#index'
end
加了小括號,就變成是 Optional,可以是 127.0.0.1:3000/zh/posts
,也可以是 127.0.0.1:3000/posts
,如果不填語區,那就是 default locale
可以用 '/:locale'
,這樣就會是 127.0.0.1:3000/zh
# config/routes.rb
get '/:locale', to: 'home#index'
你可以將使用者偏好的語系紀錄在資料庫(Database)裡,等使用者過來瀏覽網頁時,再從資料庫撈出該使用者相對應的語系,並依照該語系呈現頁面給使用者,就可以改成以下這樣
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
- locale = params[:locale] || I18n.default_locale
+ locale = current_user.try(:locale) || params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
end
還可以從 request 的 header 中抓出可能的答案,現在這時代的瀏覽器,會將使用者設定過的語系,紀錄在瀏覽器中,在傳送 HTTP request 時,以 Accept-Language
將此設定一同傳出給 server
你可以在 controller 中下中斷點,並使用 pry(xxx)> request.accept_language
,得到 => "zh-TW,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6”
類似的答案,再利用正規表達式之類的方法,可以爬出 zh
或是 en
等較大的語系
所以在 controller 內,就可以用以下方式判斷使用者偏好的語系
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
around_action :switch_locale
def switch_locale(&action)
- locale = current_user.try(:locale) || params[:locale] || I18n.default_locale
+ locale = current_user.try(:locale) || request.accept_language.scan(/^[a-z]{2}/).first || params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
end
也可以使用 request.env['HTTP_ACCEPT_LANGUAGE']
,和 request.accept_language
是一樣的東西
以上雖然可行,但實際上 request 的 HTTP_ACCEPT_LANGUAGE
不一定會長這樣,所以有個 gem 專門在做偵測使用者語系偏好的事,如果有這個需求的話,可以直接使用 iain/HttpAcceptLanguage 這個 gem,或是使用 Rack-Contrib 這個 middleware 的 locale
除了從 Accept-Language
抓,也可以從 IP 所對應到的國家,來決定預設語言,這部分也可以借助已有的 alexreisner/geocoder gem 幫忙解析出國家。
舉例,多國的貨幣就很適合這個情形,可以在設定檔裡面保留變數的位置
# config/locales/zh.yml
zh:
currency: "NT$ %{price}"
# config/locales/en.yml
en:
currency: "%{price} USD"
事後再將變數帶入即可
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<%= content_tag(:p, t(:currency, price: 100)) %>
今天寫的是 i18n 的基礎用法,剩下的還有寫到
看完這篇 RailsGuide 的感想是,要做多國好麻煩,尤其是支援越多國家的時候,你要新增的頁面的文案就要每個國家都改,超級麻煩,就這樣XD,我們明天見~