I18n

Phong cách GNU gettext được sử dụng. Không giống như các cách i18n khác, gettext hỗ trợ nhiều định dạng khác nhau.

_images/poedit.png

Viết các internationalized messages vào source code

xitrum.Action kế thừa xitrum.I18n, và khi đó có 2 method sau:

t("Message")
tc("Context", "Message")

t("Hello %s").format("World")

// 1$ and 2$ are placeholders
t("%1$s says hello to %2$s, then %2$s says hello back to %1$s").format("Bill", "Hillary")

// {0} and {1} are placeholders
java.text.MessageFormat.format(t("{0} says hello to {1}, then {1} says hello back to {0}"), "Bill", "Hillary")

t("%,.3f").format(1234.5678)                                // => 1,234.568
t("%,.3f").formatLocal(java.util.Locale.FRENCH, 1234.5678)  // => 1 234,568
// Above, you explicitly specify locale.
// If you want to implicitly use locale of the current action:
// when English => 1,234.568, when French => 1 234,568
t("%,.3f", 1234.5678)

Bạn có thể gọi trực tiếp 2 method trên từ trong action. Trong khi các nơi khác như model, bạn cần truyền current action vào đó và gọi ttc.

// In an action
respondText(MyModel.hello(this))

// In the model
import xitrum.I18n
object MyModel {
  def hello(i18n: I18n) = i18n.t("Hello World")
}

Triển khai các message đến tập tin pot

Tạo một tệp i18n.pot trong thư mục gốc của project, sau đó biên dịch lại cả project.

sbt/sbt clean
rm i18n.pot
touch i18n.pot
sbt/sbt compile

Lệnh sbt/sbt clean dùng để xóa tất cả các tệp .class , yêu cầu SBT biên dịch lại cả project. Vì sau sbt/sbt clean, SBT sẽ thử tải lại toàn bộ dependencies, bạn có thể tiến hành nhanh hơn một chút với lệnh find target -name *.class -delete, nó sẽ xóa toàn bộ các tệp . class trong thư mục target.

Sau khi biên dịch lại, i18n.pot sẽ được lấp đầy với các gettext message từ mã nguồn. Để làm điều này, Scala compiler plugin technique được sử dụng.

Tuy nhiên, phương pháp này sẽ chỉ trích rút dữ liệu từ mã nguồn. Nếu bạn có các tập tin Java, bạn có thể sử dụng câu lệnh xgettext để trích xuất dữ liệu:

xgettext -kt -ktc:1c,2 -ktn:1,2 -ktcn:1c,2,3 -o i18n_java.pot --from-code=UTF-8 $(find src/main/java -name "*.java")

Sau đó bạn gộp tệp i18n_java.pot và tệp i18n.pot.

Lưu các tệp .po tại đâu

i18n.pot là một tệp bản mẫu. Bạn cần sao chép nó đến tệp <language>.po và dịch.

Xitrum theo dõi thư mực có tên i18n trong classpath. Nếu một tệp <language>.po trong thư mục đó được thay đổi hoặc được thêm vào ở runtime, Xitrum sẽ tự động tải lại tệp <language>.po đó.

src
  main
    scala
    view
    resources
      i18n
        ja.po
        vi.po
        ...

Sử dụng công cụ như Poedit để edit các tệp .po. Bạn cũng có thể sử dụng nó để hợp các tệp pot mới vào tệp po cũ.

_images/update_from_pot.png

Bạn có thể đóng gói các tệp .po trong nhiều tệp JAR. Xitrum sẽ tự động gộp chúng khi chạy.

mylib.jar
  i18n
    ja.po
    vi.po
        ...

another.jar
  i18n
    ja.po
    vi.po
        ...

Chọn ngôn ngữ

  • Để lấy cấc ngôn ngữ trong Accept-Language request header bởi browser, gọi hàm browserLanguages. Kết quả sẽ được sắp xếp theo mức ưu tiên đặt bởi trình duyệt từ cao xuống thấp.
  • Ngôn ngữ mặc định là “en”. Để chuyển ngôn ngữ, ví dụ Nhật Bản, gọi language = "ja"
  • Để tự đặt ngôn ngữ phù hợp nhất trong resource, gọi autosetLanguage(availableLanguages), với availableLanguages là một list các ngôn ngữ có trong thư mục resources/i18n và các tệp JAR. Nếu không có ngôn ngữ nào phù hợp, ngôn ngữ vẫn mặc định là “en”.
  • Để lấy ngôn ngữ hiện thời được đặt bên trên, sử dụng language.

Trong action, thông thường trong một before filter, để đặt ngôn ngữ:

beforeFilter {
  val lango: Option[String] = yourMethodToGetUserPreferenceLanguageInSession()
  lango match {
    case None       => autosetLanguage(Locale.forLanguageTag("ja"), Locale.forLanguageTag("vi"))
    case Some(lang) => language = lang
  }
}

Validation messages

jQuery Validation plugin cung cấp i18n error messages. Xitrum tự động thêm các tệp message tương ứng vào ngôn ngữ hiện thời.

Với validator mặc định ở phía server trong package xitrum.validator, Xitrum cũng cung cấp bản dịch tương ứng.

Với đa số form

tn("Message", "Plural form", n)
tcn("Context", "Message", "Plural form", n)

Xitrum chỉ có thể chạy đúng với đa số form sau:

Phần lớn các form thường nằm trong số sau:

nplurals=1; plural=0
nplurals=2; plural=n != 1
nplurals=2; plural=n>1
nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2
nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2
nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2
nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2
nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2
nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2
nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2
nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3

Định dạng ngày và số

Nếu bạn sử dụng Scalate template engine, mặc định ngày và số sẽ được định dạng theo ngôn ngữ hiện thời.

Nếu bạn muốn sử dụng định dạng khác:

import java.text.{DateFormat, NumberFormat}

val myDateFormat   = ...
val myNumberFormat = ...
val options        = Map("date" -> myDateFormat, "number" -> myNumberFormat)
respondView(options)