Internationalization의 축약형

국제화, Internationalization은 알파벳이 20개인데, 가장 마지막 글자인 n 사이에 알파벳이 18개가 있다고 하여 i18n 이라 한다.
Front End 에서 주는 JsonData 의 값은
{
"content": [
{
"Key": "common.button.confirm",
"ko-KR": "확인",
"en-US": "Confirm"
},
{
"Key": "common.button.delete",
"ko-KR": "삭제",
"en-US": "Delete"
},
{
"Key": "common.button.register",
"ko-KR": "등록",
"en-US": "Register"
},
{
"Key": "common.button.list",
"ko-KR": "목록",
"en-US": "List"
}
]
}
형태로 전달을 해준다고 하였다.
여기서 가장 중요했던 점은, 언어에 대한 번역에 유연해야 한다는 것 이였다.
그래서 DB 설계를 할 때 그 부분을 중점으로 개발을 하였다.
데이터에 대한 컬럼은
- language_code
- tanslation_key
- tanslation_value
이 세가지 컬럼이다.
- language_code 는 언어에 대한 key 값이 들어가게 된다.
- ex) ko-KR, en-US
- tanslation_key는 전달 받은 Request의 Key 값이 들어가게 된다.
- ex) common.button.list , common.button.register
- translation_value는 전달 받은 Request의 언어에 맞는 Value 값이 들어간다.
- ex) 목록, List, 등록, Register
이러한 저장에 대한 특이성이 있어 Request 와 Response 에서는 Json Data 에 값은 형태를 유지하고 저장에 대한 값을 위한 캐스팅이 필요했다.
업로드
public ResponseResult<Long> createTranslations(TranslationsUploadRequest translationsUploadRequest) {
deleteTranslations();
JsonNode content = translationsUploadRequest.getContent();
ObjectMapper mapper = new ObjectMapper();
Long saveCount = 0L;
try {
List<Map<String, String>> list = mapper.readValue(content.traverse(), new TypeReference<List<Map<String, String>>>() {
});
for (Map<String, String> map : list) {
Set<String> keys = map.keySet();
String keyValue = map.get("Key");
for (String key : keys) {
if (!"Key".equals(key)) {
Translations translations = Translations.builder()
.languageCode(key)
.translationKey(keyValue)
.translationValue(map.get(key))
.build();
Translations save = translationsRepository.save(translations);
if (save != null) {
saveCount++;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return ResponseResult.response(saveCount, ResponseStatus.CREATE);
}
- 들어오는 Data 는 JsonNode 의 자료형으로 받게 된다.
- 기존의 형태를 가져와 List<Map<String, String>> 의 형태로 캐스팅을 진행하게 된다.
- 그리고 가져온 리스트에 Map 을 반복문으로 추출하게 된다.
- Map의 추출에서 Key 값을 추출하여 Key 값 중 고정으로 들어가는 Key의 값을 기준으로 잡고 Builder를 진행하게 된다.
- Key 값은 translationKey 의 값으로 저장하게 된다.
- 그리고 Map 의 Key 값이 Key 가 아닐 시의 값으로 languageCode 와 translationValue
- 이렇게 하면 하나의 Key 값은 LoagugeCode의 수 만큼 저장되게 되어 원하는 Translations 객체가 만들어 지게 된다.
- Request JSON Data
{
"content": [
{
"Key": "common.button.confirm",
"ko-KR": "확인",
"en-US": "Confirm"
},
{
"Key": "common.button.delete",
"ko-KR": "삭제",
"en-US": "Delete"
}
]
}
- 결과물

다운로드
@Cacheable
public ResponseResult<ArrayNode> downloadTranslations(){
List<Translations> allTranslations = translationsRepository.findAll();
Map<String, List<Translations>> groupedTranslations = allTranslations.stream()
.collect(Collectors.groupingBy(Translations::getTranslationKey));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
ArrayNode arrayNode = objectMapper.createArrayNode();
groupedTranslations.forEach((key, translationsList) -> {
Map<String, String> translationData = new HashMap<>();
translationData.put("Key", key);
translationsList.forEach(translation ->
translationData.put(translation.getLanguageCode(), translation.getTranslationValue())
);
arrayNode.add(objectMapper.valueToTree(translationData));
});
return ResponseResult.response(arrayNode,ResponseStatus.SUCCESS);
}
- 다운로드는 모든 데이터를 다 출력해야 되기 때문에 DataJPA 를 이용하여 findAll()을 이용하여 모든 데이터를 가져온다.
- 그 다음 stream.collect를 이용해 데이터를 Map으로 캐스팅 해준다.
- 캐스팅을 진행 해줄 때 translationKey를 이용해 grouping 을 같이 진행 시켜 준다.
- 그 후 Map 형태의 데이터를 key를 기준으로 forEach를 이용해 반복문을 진행 시켜 ArrayNode 형태로 캐스팅을 진행 시켜 준다.
- 그리고 데이터의 key,value 형식으로 넣어주는데 key 값에는 languageCode, value 값에는 translationsValue 값이 들어가게 된다.
최종적으로
{
"content": [
{
"Key": "common.button.confirm",
"ko-KR": "확인",
"en-US": "Confirm"
},
{
"Key": "common.button.delete",
"ko-KR": "삭제",
"en-US": "Delete"
}
]
}
이 형태로 데이터가 출력이 된다.
목록
@Cacheable
public ResponseResult<Page<JsonNode>> listTranslations(TranslationsSearchCondition translationsSearchCondition, Pageable pageable) {
int pageSize = (int) (translationsRepository.groupByLanguageCode(translationsSearchCondition) * pageable.getPageSize());
Map<String, List<Translations>> groupedTranslations = translationsRepository.translationsGroup(translationsSearchCondition, PageRequest.of(offSet(pageable.getOffset()), pageSize));
if (groupedTranslations == null) {
return ResponseResult.response(null, ResponseStatus.NO_CONTENTS);
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
ArrayNode arrayNode = objectMapper.createArrayNode();
groupedTranslations.forEach((key, translationsList) -> {
Map<String, String> translationData = new HashMap<>();
translationData.put("Key", key);
translationsList.forEach(translation ->
translationData.put(translation.getLanguageCode(), translation.getTranslationValue())
);
arrayNode.add(objectMapper.valueToTree(translationData));
});
List<JsonNode> listFromNode = new ArrayList<>();
arrayNode.forEach(listFromNode::add);
Long totalCount = translationsRepository.translationsCount(translationsSearchCondition);
LongSupplier longSupplier = () -> totalCount;
Page<JsonNode> page = PageableExecutionUtils.getPage(listFromNode, pageable, longSupplier);
return ResponseResult.responseTranslations(page, totalCount, ResponseStatus.SUCCESS);
}
- 목록의 가장 큰 이슈는 페이징 과 검색 이였다.
- Page 에 부분은 기존의 하나의 Json Data 가 DB에 저장 될때는 Language의 수 만큼 저장이 되기 때문에 기존 입력한 페이징에 대한 정보를 다시 불러오고, Language Code로 그룹화하여 그 수를 가져온 쿼리로 곱하여 페이징을 진행하게 된다.
- 그렇게 한 다음 최종 데이터는 다시 Json Data 와 같은 형태로 나가기 때문에 Page에 요청한 페이징 데이터가 출력된다.
- ex) offset : 1 /page : 10 → offset:1 / page : 10 * (group Language 수 = 2) = 20
- 검색 조건은 2개가 존재 했다.
- key 값으로 조회
- value 값으로 조회
- 하지만 여기서 문제점은 value 값으로 조회를 했을 때, 한글명에 대한 값을 조회해도 이거에 매칭되는 영문의 값도 같이 가져와야 되는것이였다.
- 그러기 때문에 검색해도 한가지 로직이 필요했다.
- 먼저 value 값으로 검색을 하여 key 값을 가져온다.
- 가져온 Key 값으로 다시 매칭되는 list 를 가져온다.
private BooleanExpression translationValueContains(String translationValue) {
return hasText(translationValue) ? translations.translationKey.in(getTranslationValue(translationValue)) : null;
}
private BooleanExpression translationValueContainsGetKey(String translationValue) {
return hasText(translationValue) ? translations.translationValue.contains(translationValue) : null;
}
private List<String> getTranslationValue(String translationValue) {
return queryFactory
.select(translations.translationKey)
.from(translations)
.where(
translationValueContainsGetKey(translationValue)
).fetch();
}
- 이렇게 Data를 불러 오게 된다.
- 그 후 Map 형태의 데이터를 key를 기준으로 forEach를 이용해 반복문을 진행 시켜 ArrayNode 형태로 캐스팅을 진행 시켜 준다.
- 그리고 데이터의 key,value 형식으로 넣어주는데 key 값에는 languageCode, value 값에는 translationsValue 값이 들어가게 된다.
'Spring boot' 카테고리의 다른 글
| Error Response 작업 (0) | 2023.09.05 |
|---|---|
| Request Test (0) | 2023.08.01 |
| EnumVaild Anotation (0) | 2023.08.01 |
| ResponseEntity Code Convention (0) | 2023.07.26 |
| ResponseResult 개발 진행 (0) | 2023.07.26 |
댓글