使用 i18n 設計多語系 API 資訊
隨著網絡使用者的多樣性和國際化程度的提高,網站開發者開始關注如何更好地為不同的用戶提供服務。這就需要網站能夠適應不同的語言和文化。使用i18n技術可以實現這一目標,它可以讓網站自動識別用戶的語言環境,並根據用戶的語言環境提供相應的內容和界面。
然而,若將語系的設定放在 API 端點 (URL) 中,容易使得 API 的端點較難閱讀,易造成後續接手的人力;因此今天的主題也會介紹Accept-Language 這個 Header 的用途,以及如何簡化 RESTful API 端點路徑
介紹的大綱如下:
1. 介紹 Accept-Language
2. 實作 Accept-Language 相關設定檔
3. 測試 API 回應
介紹 Accept-Language:
Accept-Language 是一個HTTP標頭,用於在客戶端和伺服器之間進行語言協商。當瀏覽器或其他客戶端向伺服器發送HTTP請求時,它可以包含 Accept-Language 標頭,以指示其首選的自然語言。
Accept-Language 標頭的值是一個由語言標籤組成的列表,按優先順序排列。語言標籤通常是根據ISO 639-1標準(例如 "en"代表英文,"zh"代表中文),並可以通過連字符進一步指定區域變體(例如 "en-US"代表美國英文,"zh-TW"代表台灣中文)。
當伺服器收到包含 Accept-Language 標頭的請求時,它可以使用該信息來決定要回應的內容語言。伺服器可以檢查支持的語言列表,並選擇最匹配的語言來提供回應。如果伺服器無法提供請求的首選語言,它可以回應默認語言或其他可用的語言。
實作 Accept-Language 相關設定檔:
使用環境如下:
Java: JDK 1.8
IDE: Intellij IDEA
Spring boot: 2.7.12
I18nConfig.java 設定 I18N 必要的 bean (MessageSource, LocaleResolver)
/**
* 設定 i18n 設定
*/
@Configuration
public class I18nConfig {
/**
* Application 支持的語系
*/
@Value(value = "${locale.supported}")
private List<String> supportLocales;
/**
* Application default 的語系
*/
@Value(value = "${locale.default}")
private String defaultLocale;
/**
* 解析當前 application 的語系
* @return
*/
@Bean
public LocaleResolver getLocaleResolver(){
CustomLocaleResolver localeResolver = new CustomLocaleResolver();
localeResolver.setDefaultLocale(LocaleUtils.toLocale(defaultLocale));
localeResolver.setSupportedLocales(supportLocales.stream().map(l-> LocaleUtils.toLocale(l)).collect(Collectors.toList()));
return localeResolver;
}
/**
* 讀取語系檔
* @return
*/
@Bean(name="messageSource")
public MessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("i18n/message");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
WebConfig.java 設定 Interceptor (攔截器)
/**
* MVC 設定
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 讀取 Accept-Languange 並 assign 語系至 LocaleContextHolder
*/
@Autowired
private HeaderInterceptor localeInterceptor;
/**
* 註冊 interceptor
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor).addPathPatterns("/**");
}
}
HeaderInterceptor.java :再每一次的 request 會檢查 Accept-Language 是否存在,若無帶入 Accept-Language 則顯示繁體中文語系(Default 語系),反之則依照 Accept-Language 的語系帶入
@Component
public class HeaderInterceptor extends LocaleChangeInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException {
String acceptLanguage = request.getHeader("Accept-Language");
Locale locale = null;
if(StringUtils.isBlank(acceptLanguage)){
locale = Locale.forLanguageTag("zh-TW");
}else {
locale = Locale.forLanguageTag(acceptLanguage);
}
LocaleContextHolder.setLocale(locale);
return true;
}
}
LanguageUtil.java : 用於封裝 messageSource 的訊息翻譯檔,以利於重用 MessageSource
/**
* 用於操作 i18n 相關函數
*/
@Component
public class LanguageUtil {
/**
* 轉換 i18n 的訊息
*/
@Autowired
private MessageSource messageSource;
/**
* 依照現在語系取得字幕檔
* @param messageKey 鍵值
* @return 字幕
*/
public String getMessage(String messageKey){
return messageSource.getMessage(messageKey, null, LocaleContextHolder.getLocale());
}
/**
* 依照現在語系取得字幕檔
* @param messageKey 鍵值
* @param params 參數
* @return 字幕
*/
public String getMessage(String messageKey, String... params){
return messageSource.getMessage(messageKey, params, LocaleContextHolder.getLocale());
}
}
測試 API 回應:
本篇使用 Postman 進行 api 測試
繁體中文 (Accept-Language 為 zh-TW 或沒帶 Accept-Language) | 英文 | 日文 |
結論:相較於Spring i18n 內建的語言轉換需要用 lang=en 的方式切換語言,Accept-Language 可以有效減少 endpoint 閱讀的複雜度;若有碰到 API 的回應訊息需要使用多語系的情況,也可以嘗試用 Accept-Language 的標頭切換語系,讓 API 的端點路徑更精簡
範例:Repository連結
參考資料:
1. https://springframework.guru/internationalization-with-spring-boot/
2. https://saurav-samantray.medium.com/internationalization-of-spring-boot-restful-services-a9103e07ca9e