iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
Modern Web

Rails,我要進來囉系列 第 21

第二十一天:網頁如何支援多國語言?Rails 的 i18n

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,前幾天跟大家一起做了很多 Rails 前端的實驗,今天回歸一下 RailsGuide 還沒看的內容,今天要看的是 Rails 的 Internationalization API,縮寫為 i18n API,這個縮寫邏輯跟 kubernetes = k8s 一樣,就是把太長的英文字,中間有幾個字母就用數字代替。

我其實很不熟 i18n,在我的 side project 中,也沒有必須要做多國語言的硬需求,所以也沒花時間去研究,今天就來看看,夠夠~

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day21-1.png

初始設置

  1. 在 Gemfile 新增 rails-i18n gem,並執行 $ bundle install

    # Gemfile
    
    gem 'rails-i18n'
    
  2. 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
    

    我們設定可用語言是英文跟中文,並且預設語言是英文

  3. 手動新增 config/locales/zh.yml,新增一個中文的對照表

    # config/locales/zh.yml
    zh:
    	hello: '哈囉握的'
    

    可以打開同一資料夾的 config/locales/en.yml 比較一下

    # config/locales/zh.yml
    en:
    	hello: 'Hello world'
    

使用方法

會使用多國語言的情境,大概就是

  1. 前端頁面顯示:根據不同國家的使用者,呈現不同語言的網頁內容
  2. Email 寄信:根據不同國家的使用者,寄出使用者看得懂的語言
  3. API Response 內容:根據不同國家的使用者,回傳適合使用者的語言的資料內容

API Response 和 前端頁面顯示

  1. 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
    
  2. 這樣使用者只要在 127.0.0.1:3000?locale=zh,就能看到中文內容囉

但我就問,使用者怎麼會知道要這樣加!? 難道要開發者每個連結都要記得加上這個 locale 的參數嗎?所以 Rails 提供一個很方便的功能,請繼續看下去。

透過 Default URL Options,連結自動帶 locale 參數

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 的參數,就像下面這樣

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day21-2.png

如果你原本的頁面就是沒有帶 locale 參數的話,Rails 將會使用當初設定的 config.i18n.default_locale 當作預設的 url 參數

再假如,如果你已經引導使用者到中文頁面了,那麼這時候產生的連結,會自動帶上中文的 locale,這樣的設定下,對開發者來說是挺方便的,就像下面這樣~

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day21-3.png

想要另一種形式的 routes

又或者是你覺得用參數醜醜的,想要直接變成網址的一部分,變成像是 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 的基礎用法,剩下的還有寫到

  • 根據不同語系設定不同的時間格式
    • e.g. 英文:2022/09/28 vs. 中文:2022年9月28日
  • ActiveRecord Model 的單複數
  • ActiveRecord 的 model attributes
  • ActiveRecord validates 的錯誤訊息
  • ActionMailer 的 Email 多語
  • 可自訂的 i18n backend,雖然好像沒提到有哪些 backend 可以選擇

看完這篇 RailsGuide 的感想是,要做多國好麻煩,尤其是支援越多國家的時候,你要新增的頁面的文案就要每個國家都改,超級麻煩,就這樣XD,我們明天見~


上一篇
第二十天:在 Rails 7 + React JS 做出圈圈叉叉(Tic Tac Toe)
下一篇
第二十二天:Rails 官方內建的測試框架 - Minitest
系列文
Rails,我要進來囉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言