在實作多國語言的Web服務時,會需要去讀取客戶端傳送來的HTTP請求標頭中的Accept-Language欄位,來判斷客戶端偏好使用的語言。然而,Rocket框架並未內建解析Accept-Language欄位的功能,開發者只能自行去撰寫程式來想辦法用Accept-Language欄位的原始資料去做客戶端的語言判斷,不但麻煩又很不安全。



一個Accept-Language欄位的值可能如以下這樣:

zh-TW,zh;q=0.7,en-US;q=0.6,en;q=0.5

以上的值,代表客戶端想要優先使用台灣的繁體中文,如果沒有的話就使用任意地區的中文,如果還是沒有的話就使用美國的英文,如果又沒有的話就使用任意地區的英文。

accept-language Request Guard for Rocket Framework

「accept-language Request Guard for Rocket Framework」是筆者開發出來的套件,將可以讀取Accept-Language欄位並且進行語言判斷的功能寫成請求守衛,一勞永逸。使用accept-language這個crate來進行Accept-Language欄位值的解析,並轉成unic-langid這個crate提供的LanguageIdentifier結構體的實體來儲存。

Crates.io

https://crates.io/crates/rocket-accept-language

Cargo.toml

rocket-accept-language = "*"

使用方法

在Rocket框架啟動前,先建立出LanguageIdentifier結構實體的陣列,用來表示這個Rocket應用程式支援的語言。接著再把這個LanguageIdentifier陣列,註冊成Rocket的應用程式狀態。unchecked_language_region_pairs巨集能以很小的開支(overhead)來建立Vec<LanguageIdentifier>結構實體。

在Rocket框架的路由處理程序中,藉由放置rocket_accept_language這個crate所提供的AcceptLanguage請求守衛,來解析HTTP請求標頭中的Accept-Language欄位。AcceptLanguage的結構實體提供了get_appropriate_language_region方法,可以傳入一個LanguageIdentifier陣列切片,根據Rocket應用程式有支援的語言來判斷出最符合HTTP請求的語言。

以下是一個使用範例:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

#[macro_use]
extern crate rocket_accept_language;

use rocket::State;

use rocket_accept_language::{AcceptLanguage, LanguageIdentifier};

struct SupportLanguages {
    language_identifiers: Vec<LanguageIdentifier>
}

impl SupportLanguages {
    fn as_language_identifiers(&self) -> &[LanguageIdentifier] {
        &self.language_identifiers
    }
}

#[get("/")]
fn hello(accept_language: &AcceptLanguage, support_languages: State<SupportLanguages>) -> &'static str {
    let (language, region) = accept_language.get_appropriate_language_region(support_languages.as_language_identifiers()).unwrap_or(("en", None));

    match language {
        "zh" => match region.unwrap_or("") {
            "CN" => "哈罗!",
            _ => "哈囉!",
        }
        "jp" => "ハロー",
        _ => "Hello!"
    }
}

fn main() {
    let support_languages = SupportLanguages {
        language_identifiers: unchecked_language_region_pairs![
            zh-TW,
            zh-CN,
            jp,
            en,
        ]
    };

    rocket::ignite()
        .manage(support_languages)
        .mount("/", routes![hello])
        .launch();
}