Hướng dẫn xây dựng web site đa ngôn ngữ bằng Rails

Hiện nay, hều hết các trang web nổi tiếng mà bạn vào hằng ngày, bạn có thể dễ thấy được phần lựa chọn ngôn ngữ hiển thị cho trang web, có rất nhiều thứ tiếng có thể chọn như tiếng Anh, Pháp, Việt Nam, Nhật Bản ... Và sau khi bạn chọn chế độ ngôn ngữ hiển thị nào thì từ đó về sau tất cả nội dung mà trang web đó trả ra cho bạn sẽ có nội dung viết bởi ngôn ngữ bạn đã chọn. Vậy làm sao để có thể dưng dựng được trang web như vậy, tôi sẽ hướng dẫn các bạn ngay sau đây, và không chỉ có 1 cách, chúng ta có tới hản vài cách để cho các bạn lựa chọn 😉

OK, chúng hãy bắt tay vào làm thôi nào 😄.

Đầu tiên giống như mọi lần, chúng ta sẽ phải tạo 1 app rails

rails new webshop
[...]
cd webshop

Tiếp theo là tạo controller và view

rails generate controller Page index
[...]

Chỉnh lại config routes để trỏ tới trang web mình vừa tạo

Webshop::Application.routes.draw do
  root "page#index"
  get "page/index"
end

thêm nội dung cho view vừa tạo

// app/views/page/index.html.erb
<h1>Example Webshop</h1>
<p>Welcome to this webshop.</p>

<p><b>I18n.locale:</b>
<%= I18n.locale %>
</p>

Ok, sau đó ta hãy chạy thử server và xem kết quả in ra

rails s

Bật trang web và truy cập địa chỉ http://localhost:3000/ bạn sẽ thấy Screenshot from 2016-01-25 09:36:27.png

các bạn có thể thấy mặc định khi vào rails sẽ là ngôn ngữ tiếng Anh, để thay đổi ngôn ngữ mặc định ở file config/application.rb

#config/application.rb
# 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 = :vn

Sau đó restart lại server và reloaf lại trang web bạn sẽ thấy trang web thay đổi phần ngôn ngữ Screenshot from 2016-01-25 09:55:24.png

Nhưng mà chúng ta muốn thay đổi nội dung text hiện thị mà, nãy giờ đâu có thấy phần nội dung thay đổi đâu nhỉ?

=> Đó là do ta đang fix cứng nội dung trả ra, để dặt đa ngôn ngữ chúng ta sửa lại file view như sau

// app/views/page/index.html.erb
<h1><%= I18n.t "page.index.title" %></h1>
<p><%= I18n.t "page.index.welcome" %></p>

<p><b>I18n.locale:</b>
<%= I18n.locale %>
</p>

kế tiếp tạo file chưa nội dung ngôn ngữ tiếng Việt

#config/locales/vn.yml
vn:
  hello: "xin chào"
  page:
    index:
      title: "hướng dẫn tạo web "
      welcome: "chào mừng đến trang web"

Tiện tay ta sẽ tạo luôn phần nội dung tiếng Anh

#config/locales/en.yml
en:
  hello: "Hello world"
  page:
    index:
      title: "Example Webshop"
      welcome: "Welcome to this webshop."

Sau đó bạn tải lại trang web sẽ thấy nội dung thay đổi:

Screenshot from 2016-01-25 10:20:57.png

Mình sẽ giải thích 1 chút ở đây, ở rail có sẵn hỗ trợ ngôn ngữ thông qua I18n, nếu ta set config.i18n.default_locale = :vn, thì lúc ta gọi lệnh rails I18n.t "pages.index.title" ở view, nó sẽ lấy text từ file vn.yml (ứng với default_locale, nếu ta xét config.i18n.default_locale = :de thì rails sẽ tìm đến file de.yml tương ứng), tiếp đó sẽ lấy phần text ứng với pages.index.title đã khai báo ở trong file là "hướng dẫn tạo web" để hiện ra view.

Chú ý: Ở đây ta có thể viết rút gọn view thành

// app/views/page/index.html.erb
<h1><%= t ".title" %></h1>
<p><%= t ".welcome" %></p>

<p><b>I18n.locale:</b>
<%= I18n.locale %>
</p>

t ở đây sẽ thay cho I18n.t ở cả view và controller (còn ở model bán sẽ phải viết đủ I18n.t)

Còn ".title" thì rails tự tìm đến "page.index.title" dựa vào địa chỉ của file html tính từ thư mục views (page/index.html.erb)

Okie, đã xong phần nội dụng đa ngôn ngữ, giờ a sẽ làm tiếp sang phần chuyển đổi I18n.locales. Ở đây mình có 3 giải pháp sau

Cách 1 Dựa vào tiền tố trên url

chúng ta sửa lại file config/routes.rb sau

Webshop::Application.routes.draw do

  scope "(:locale)", :locale => /en|vn/ do
    root "page#index"
    get "page/index"
  end

end

tiếp đó ta cần phải xét lại default ngôn ngữ trên controller và xử lý trước khi xuống views:

class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :set_locale

  private
  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end

sau đó thử truy cập lại đường dẫn http://localhost:3000/vn ta sẽ thấy web site hiện thị đoạn với nội dung tiếng việt Screenshot from 2016-01-25 11:09:47.png

Tiếp tục với đường dẫn http://localhost:3000/en ta sẽ có nội dung tiếng anh tương ứng Screenshot from 2016-01-25 11:10:03.png

Giải thích: ở phần routes bạn có thẻ thấy ta đã thêm 1 scope để truyền params locale lên (chỉ chấp nhận 2 loại en và vn). Và sau đó ở trong controller application chúng ta đã viết hàm set_locale để kiểm tra params[:locale] truyền lên rồi định nghĩa lại "I18n.locale" trong request đó là dùng ngôn ngữ nào để hiển thị.

Để có thể chuyển ngôn ngữ trên view, ta thêm chức năng chuyển ngôn ngữ thông qua việc add link ở view.

Ở file app/views/layouts/application.html.erb ta thay đổi thành:

// app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Webshop</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<p>
<%= link_to_unless I18n.locale == :en, "English", locale: :en %>
|
<%= link_to_unless I18n.locale == :vn, "VietNam", locale: :vn %>
</p>

<%= yield %>

</body>
</html>

Tuy nhiên có 1 vấn đề khi dùng tiền tố cho url đó là các đoạn dùng link_to sẽ tự động bỏ đoạn tiền tố đi và do đó trang web sẽ sử dụng lại ngôn ngữ mặc định để hiển thị.

Giải pháp cho vấn đề này là chúng ta cần chèn thêm đoạn code sau Rails.application.routes.default_url_options[:locale]= I18n.locale vào file app/controllers/application_controller.rb để tiền tố cũ sẽ tự động đc thêm vào khi ta chuyển link url


#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :set_locale

  private
  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
    Rails.application.routes.default_url_options[:locale]= I18n.locale
  end
end

Cách 2 Dùng Header của trình duyệt

Cách này có 1 ưu điểm hơn cách trên đó là người dùng không cần đánh đuôi /vn,/en mà ta sẽ dựa vào thông tin có trong header của request để xác định ngôn ngữ của người dùng, cũng như chọn đc ngôn ngữ ưu tiện hiện khi user vào trang web lần đầu tiên (tham khảo thêm ở https://www.w3.org/International/questions/qa-lang-priorities)

Để lấy thông tin ngôn ngữ dựa vào header ta làm như sau.Vào file app/controllers/application_controller.rb sửa lại thành

#app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :set_locale

  private
  def extract_locale_from_accept_language_header
    request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first # lọc lấy mã ngôn ngữ đc ghi trong header
  end

  def set_locale
    I18n.locale = extract_locale_from_accept_language_header || I18n.default_locale # gán ngỗn ngữ đó là mặc đinh, nếu trong header ko có thì ta sẽ sử dụng ngôn ngữ mặc định trong con
  end
end

chú ý: bạn phải bỏ đoạn code về cấu hình(ở file config/routes.rb) đã viết ở cách 1 để đảm bảo code ở cách 2 này hoạt động Hạn chế: ở đây nếu muốn thay đổi ngôn ngữ thì ta phải thay đổi header request gửi lên cho server

Cách 3 sử dụng cookies

Ở đây chúng ta cần tạo 1 controller để xử lý cập nhật session locales

 $ rails generate controller SetLanguage english vietnam
  [...]

trong file app/controllers/set_language_controller.rb ta định nghĩa các hàm và action sau

#app/controllers/set_language_controller.rb

class SetLanguageController < ApplicationController
  def english
    I18n.locale = :en
    set_session_and_redirect
  end

  def german
    I18n.locale = :de
    set_session_and_redirect
  end

  private
  def set_session_and_redirect
    session[:locale] = I18n.locale # cập nhật ngôn ngữ vào cookies
    redirect_to :back # quay trở lại trang cũ với ngôn ngữ mới cập nhật
    rescue ActionController::RedirectBackError
      redirect_to :root
  end
end

Cuối cùng chúng ta cần lấy ngôn ngữ từ cookies và để set I18n.locale

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :set_locale

  private
  def set_locale
    I18n.locale = session[:locale] || I18n.default_locale
    session[:locale] = I18n.locale
  end
end

đề test chức năng này, chúng ta cần sửa lại layout/application.html.erb như sau

<!DOCTYPE html>
<html>
<head>
  <title>Webshop</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<p>
<%= link_to_unless I18n.locale == :en, "English", set_language_english_path %>
|
<%= link_to_unless I18n.locale == :de, "Deutsch", set_language_german_path %>
</p>

<%= yield %>

</body>
</html>

Ngoài ra còn 1 cách khác, đó là sử dụng tên miền .vn, .en để xác định ngôn ngữ sẽ hiển thị (nếu bạn hoặc khách hàng bạn có nhiều tên miền). Theo phương pháp này ta cần sủa lại app/controllers/application_controller.rb thành như sau

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery

  before_filter :set_locale

  private
  def set_locale
    case request.host.split(".").last
    when "vn"
      I18n.locale = :vn
    when "com"
      I18n.locale = :en
    else
      I18n.locale = I18n.default_locale
    end
  end
end

Wao nhiều cách như vậy thì tốt nhất ta nên làm theo cách nào đây! Theo mình thì cách nào thì nên dựa vào điều kiện/yêu cầu cụ thể của trang web bạn muốn làm để có thể áp dụng 1 hoặc nhiều cách với nhau. (mình thích dùng cách 2 + 3, tức là lần đầu tiên khi người dùng vào web ta bắt ngôn ngữ bằng header, rồi lưu sang cookies để sử dụng về sau)

Chúc các bạn code vui vẻ 😉)

Nguồn tham khảo: http://www.xyzpub.com/en/ruby-on-rails/3.2/i18n_mehrsprachige_rails_applikation.html

All Rights Reserved