使用 i18n 設計多語系 API 資訊

蔡仔為 Victor Tsai 2023/07/21 14:36:44
2206

隨著網絡使用者的多樣性和國際化程度的提高,網站開發者開始關注如何更好地為不同的用戶提供服務。這就需要網站能夠適應不同的語言和文化。使用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

 

蔡仔為 Victor Tsai