Ruby on Rails 實戰聖經

使用 Rails 5.0+ 及 Ruby 2.3+

電子書製作中,歡迎留下 E-mail,有消息將會通知您。若您有任何意見、鼓勵或勘誤,也歡迎來信給我。願意贊助支持的話,这是我的支付宝微信 和乙太幣 ETH 地址
0x232b7245EBE02900c21682be1e6Ad4e839751F6a

多國語系及時區

It Works on My Machine! - 數以萬計的程式設計師

多國語系

安裝Rails中文翻譯詞彙檔

Rails預設的語系是英文。要換成繁體中文,可以安裝rails-i18n這個gem有社群幫忙翻譯的繁體中文:

  • Gemfile加上gem "rails-i18n",然後執行bundle
  • 修改 config/application.rb 的預設語系

      config.i18n.default_locale = "zh-TW"
    

這樣就會使用http://github.com/svenfuchs/rails-i18n的繁體中文翻譯。

自訂翻譯檔案

要讓你的網站可以支援多國語系,必須定義出翻譯的詞彙對應檔案。這些詞彙檔放在config/locales下,使用YAML格式,例如新增一個config/locales/zh-TW.yml的檔案,內容如下:

"zh-TW":
	hello_world: 哈囉
	admin:
		event: 活動管理

注意 YAML 格式的縮排必須使用兩個空格,Tab是不允許的。直接複製貼上可能會有問題,請小心檢查縮排。

這樣就可以用I18n.t這個方法來做翻譯詞彙的替換。如果在View中可以直接使用t這個Helper方法。翻譯關鍵字可以用字串或 Symbol,也可以加上 Scope,例如:

t("admin.event")
t(:event, :scope => :admin )

I18n.t(:hello_world)  # 如果不在View中,則需要加上 I18n 類別

如果要在詞彙內嵌變數的話,可以使用%{variable_name}的語法,修改config/locales/zh-TW.yml

"zh-TW"
    hello: "親愛的%{name}你好!"

這樣在template中改成傳入參數即可:

t(:hello, :name => @user.name) # 親愛的XXX你好

就算你的網站不需要支援多國語系,這個功能對於團隊協作開發網站仍然非常有幫助,因為寫程式的時候不一定會先確定文案規格,用i18n來處理的話,最後只需要讓PM統一修改翻譯詞彙檔即可。

搭配Model使用

在套用上述的翻譯詞彙檔之後,你可能會注意到Model驗證錯誤訊息會變成如Name 不能是空白字元,如果需要近一步中文化欄位名稱,你可以新增config/locales/events.yml內容如下:

zh-TW:
  activerecord:
    attributes:
      event:
        name: "活動名稱"
        description: "描述"

其實,翻譯檔檔名叫events.ymlzh-TW.ymlen.yml什麼都無所謂,重要的是YAML結構中第一層要對應locale的名稱,也就是zh-TWRails會載入config/locales下所有的YAML詞彙檔案。

如何讓使用者可以切換多語系

在 application_controller.rb 中加入:

before_action :set_locale

def set_locale
  # 可以將 ["en", "zh-TW"] 設定為 VALID_LANG 放到 config/environment.rb 中
  if params[:locale] && I18n.available_locales.include?( params[:locale].to_sym )
    session[:locale] = params[:locale]
  end

  I18n.locale = session[:locale] || I18n.default_locale
end

在 View 中可以這樣做:

<%= link_to "中文版", :controller => controller_name, :action => action_name, :locale => "zh-TW" %>
<%= link_to "English", :controller => controller_name, :action => action_name, :locale => "en" %>

偵測瀏覽器的語系自動選擇

請參考 http://guides.rubyonrails.org/i18n.html#setting-the-locale-from-the-client-supplied-information

語系樣板

除了上述一個單字一個單字的翻譯詞彙替換之外,如果樣板內大多是屬於較為靜態的內容,Rails也提供了不同語系可以有不同樣板,你只要將樣板命名加上語系附檔名即可,例如:

app/views/pages/faq.zh-TW.html.erb
app/views/pages/faq.en.html.erb

如此在英文版的時候就會使用faq.en.html.erb這個樣板,中文版時使用faq.zh-TW.html.erb這個樣板。

時區 TimeZone

在 Rails 中,資料庫裡面的時間(datetime)欄位一定都是儲存 UTC 時間。而 Rails 提供的機制是讓你從資料庫拿資料時,自動幫你轉換時區。例如,要設定台北 +8 時區:

首先設定 config/application.rb 中預設時區為 config.time_zone = “Taipei”,如此 ActiveRecord 便會幫你自動轉換時區,也就是拿出來時 +8,存回去時 -8

如何根據使用者切換時區?

首先,你必須找個地方儲存不同使用者的時區,例如 User model 有一個欄位叫做 time_zone:string。然後在編輯設定的地方,可以讓使用者自己選擇時區:

 <%= time_zone_select :user, :time_zone %>

接著在 application_controller.rb 中加入:

before_action :set_timezone

def set_timezone
   if current_user && current_user.time_zone
      Time.zone = current_user.time_zone
    end
end

時區處理方法

Ruby原生的Time類別對於時區的處理一律是參考唯一的系統環境變數ENV['TZ'],這在使用者多時區的應用程式中就顯的見拙。因此在Rails中的時間類別使用的是ActiveSupport::TimeWithZone,我們已經知道可以使用Time.zone可以改變時區,其他的用法例如:

Time.zone = "Taipei"
Time.zone.local(2011, 8, 3, 9, 0) # 建立一個Taipei當地時間
=> Wed, 03 Aug 2011 09:00:00 CST +08:00
t = Time.zone.now # 目前時間
=> Wed, 03 Aug 2011 22:17:54 CST +08:00
t.in_time_zone("Tokyo") # 將這個時間換時區
=> Wed, 03 Aug 2011 23:18:34 JST +09:00
Time.utc(2005,2,1,15,15,10).in_time_zone # 將UTC時間換Taipei當地時間
=> Tue, 01 Feb 2005 23:15:10 CST +08:00

時間的顯示

除了使用Ruby內建的Datetime#strftime格式化時間之外,Rails也可以直接呼叫to_s轉換輸出格式:

datetime.to_s(:db)                      # => "2007-12-04 00:00:00"
datetime.to_s(:number)                  # => "20071204000000"
datetime.to_s(:short)         # => "04 Dec 00:00"
datetime.to_s(:long)          # => "December 04, 2007 00:00"
datetime.to_s(:long_ordinal)  # => "December 4th, 2007 00:00"
datetime.to_s(:rfc822)        # => "Tue, 04 Dec 2007 00:00:00 +0000"
datetime.to_s(:iso8601)       # => "2007-12-04T00:00:00+00:00"

也可以自行註冊專案常用的格式在config/initializers/time_formats.rb裡:

Time::DATE_FORMATS[:month_and_year] = '%B %Y'
Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }

或是透過I18n的機制,在翻譯詞彙檔中編輯格式,然後使用:

I18n.l( Time.now )
I18n.l( Time.now, :format => :short )

其他實務技巧

本章開頭提到欄位 (datetime) 會存成 UTC 時間,所以比對時間大小和區間的時候,無論資料當初是什麼時區,都可以正確的操作。例如:

Event.where( "created_at > ? and created_at <= ?", Time.now.beginning_of_day, Time.now.end_of_day )
# 這樣產生的 SQL 是  SELECT * FROM "events" WHERE (created_at > '2016-03-30 16:00:00' and created_at <= '2016-03-31 15:59:59')

這樣就會抓取到今天內的所有活動,注意到因為台北時區的關係,Rails 轉換成 SQL 的時候是是從 16:00 到 15:59:59。

但是如果業務需求是想要抓某一個日期或是某一個時間,而不管時區的話,用 datetime 欄位反而麻煩。例如想要抓 2016-03-31 的資料,不管是台北時區或舊金山時區,反正就是要 2016-03-31 (因為同一個時間,可能在不同時區會是不同天)。如果想要抓早上 09:00~09:59 的資料,不管是哪一個時區。這時候筆者建議你開單純的 date (只有日期)或 time (只有時間沒有日期) 資料庫欄位即可,這樣反而會比較簡單。

參考資料

》回到頁首