Spring Boot回應代碼訊息多國語言(i18n)範例。
範例環境:
- Java 8
- Spring Boot 2.2.1.RELEASE
- Spring Data JPA
- H2 database
- Lombok
- Maven
流程大致如下。當API回傳結果時,透過攔截器攔截回應主體並從中取得回應代碼至資料庫[訊息設定檔]尋找對應的語言訊息,如果資料庫沒有設定該代碼的語言訊息則使用i18n訊息檔案的內容。
建立Spring Boot專案(參考IntelliJ IDEA,Eclipse STS)。
專案的application.properites
的預設本地訊息為zh-TW
。
application.properteis
#context path
server.servlet.context-path=/demo
#port
server.port=8080
demo.default.language-tag=zh-TW
多語系預設訊息messages.properties
及中文訊息messages_zh_TW
。
messages.properties
demo.message=MessageSource auto config
demo.message.args=A message with args, arg_0={0}, arg_1={1}
demo.res.state.success=Success
demo.res.state.fail=Fail
demo.res.state.error=Error
demo.res.state.no-connection=No connection
demo.res.state.unknown=Unknown
messages_zh_TW.properties
demo.message=MessageSource自動配置
demo.message.args=帶參數的訊息,參數0={0}, 參數1={1}
demo.res.state.success=執行成功
demo.res.state.fail=執行失敗
demo.res.state.error=執行錯誤
demo.res.state.no-connection=沒有連線
demo.res.state.unknown=未知
錯誤代碼列舉ResState
,第一個參數code
為代碼,第二個參數messageKey
為取得i18n訊息的key。
ResState
package com.abc.demo.controller.dto.res;
import lombok.Getter;
import java.util.Optional;
public enum ResState {
SUCCESS("0000", "demo.res.state.success"),
FAIL("0001", "demo.res.state.fail"),
ERROR("9000", "demo.res.state.error"),
NO_CONNECTION("9001", "demo.res.state.no-connection"),
UNKNOWN("9999", "demo.res.state.unknown");
@Getter
private String code;
@Getter
private String messageKey;
ResState(String code, String messageKey) {
this.code = code;
this.messageKey = messageKey;
}
public static Optional<ResState> getResStateByCode(String code) {
for (ResState resState : ResState.values()) {
if(resState.code.equals(code)) {
return Optional.of(resState);
}
}
return Optional.empty();
}
}
對映語言訊息資料表LANGUAGE
及MESSAGE
的實體類別如下。
Language
package com.abc.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Language {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String tag;
}
Message
package com.abc.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Message {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private Long languageId;
private String key;
private String message;
}
系統啟動時H2資料庫的對LANGUAGE
及MESSAGE
的初始資料。一個LANGUAGE
對多個MESSAGE
,兩者為One-to-Many關係。
INSERT INTO LANGUAGE (ID, TAG) VALUES (1, 'zh-TW');
INSERT INTO LANGUAGE (ID, TAG) VALUES (2, 'en-US');
INSERT INTO MESSAGE (ID, LANGUAGE_ID, KEY, MESSAGE) VALUES (1, 1, 'demo.res.state.no-connection', '無連線');
INSERT INTO MESSAGE (ID, LANGUAGE_ID, KEY, MESSAGE) VALUES (2, 2, 'demo.res.state.no-connection', 'No connection');
存取實體類的repository。
LanguageRepository
package com.abc.demo.repository;
import com.abc.demo.entity.Language;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface LanguageRepository extends JpaRepository<Language, Long> {
Optional<Language> findByTag(String tag);
}
MessageRepository
package com.abc.demo.repository;
import com.abc.demo.entity.Message;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
Optional<Message> findByLanguageIdAndKey(Long languageId, String key);
}
API回應物件DemoResponse
。
DemoResponse
package com.abc.demo.controller.dto.res;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
@Builder
public class DemoResponse {
private String message;
private String code;
public static DemoResponse state(ResState resState) {
return DemoResponse.builder()
.code(resState.getCode())
.build();
}
}
Controller DemoController
。定義一個API,並用網址參數控制使用的語系。
DemoController
package com.abc.demo.controller;
import com.abc.demo.controller.dto.res.DemoResponse;
import com.abc.demo.controller.dto.res.ResState;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
public class DemoController {
@GetMapping("/message/{languageTag}")
public DemoResponse message(HttpSession httpSession, @PathVariable String languageTag) {
httpSession.setAttribute("languageTag", languageTag);
return DemoResponse.state(ResState.SUCCESS);
}
}
攔截API回傳結果的攔截器DemoResponseBodyAdvice
。要在類別名稱前加上@CongtrollerAdvice
並實作ResponseBodyAdvice
才有作用。
在beforeBodyWrite()
中取得Controller API回傳的DemoResponse
物件的回應代碼,並呼叫MessageServcie.getMessage()
取得代碼對映的語系訊息。
DemoResponseBodyAdvice
package com.abc.demo.interceptor;
import com.abc.demo.controller.dto.res.DemoResponse;
import com.abc.demo.controller.dto.res.ResState;
import com.abc.demo.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class DemoResponseBodyAdvice implements ResponseBodyAdvice<DemoResponse> {
@Autowired
private MessageService messageService;
@Override
public boolean supports(
MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.getParameterType().equals(DemoResponse.class);
}
@Override
public DemoResponse beforeBodyWrite(
DemoResponse demoResponse,
MethodParameter methodParameter,
MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
String code = demoResponse.getCode();
String localeMessage = messageService.getMessage(
ResState.getResStateByCode(code).orElse(ResState.UNKNOWN));
demoResponse.setMessage(localeMessage);
return demoResponse;
}
}
MessageServcie
用來依API傳入的語系取得資料庫對應的訊息,若無相對的訊息則從i18n訊息檔取得。
MessageService
package com.abc.demo.service;
import com.abc.demo.controller.dto.res.ResState;
import com.abc.demo.entity.Language;
import com.abc.demo.entity.Message;
import com.abc.demo.repository.LanguageRepository;
import com.abc.demo.repository.MessageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.util.Locale;
@Service
public class MessageService {
@Value("${demo.default.language-tag}")
private String defaultLauageTag;
@Autowired
private MessageSource messageSource;
@Autowired
private HttpSession httpSession;
@Autowired
private LanguageRepository languageRepository;
@Autowired
private MessageRepository messageRepository;
public String getMessage(ResState resState) {
String languageTag = (String) httpSession.getAttribute("languageTag");
Long languageId = languageRepository.findByTag(languageTag)
.map(Language::getId).orElse(0L);
if (languageId == 0) {
return messageSource.getMessage(resState.getMessageKey(), null, Locale.forLanguageTag(defaultLauageTag));
}
return messageRepository.findByLanguageIdAndKey(languageId, resState.getMessageKey())
.map(Message::getMessage)
.orElseGet(() ->
messageSource.getMessage(resState.getMessageKey(),
null,
Locale.forLanguageTag(languageTag)));
}
}
測試API並帶入不同的語系及回傳結果如下。
GET | http://localhost:8080/demo/message/zh-TW
{
"message": "執行成功",
"code": "0000"
}
GET | http://localhost:8080/demo/message/en-US
{
"message": "Success",
"code": "0000"
}
GET | ttp://localhost:8080/demo/message/wryyyyyy
{
"message": "執行成功",
"code": "0000"
}
完整程式碼請參考github。
沒有留言:
張貼留言