오늘은 다국어사용에 대해 이야기해보겠습니다. 그래서 그것은 무엇입니까?
다국어화, 즉
국제화는 프로그램 로직을 변경하지 않고 여러 언어에 적용할 수 있는 애플리케이션 개발의 일부입니다. 상황을 생각해 보십시오. 예를 들어 러시아어, 영어, 스페인어 등의 언어를 사용하는 여러 국가의 거주자를 대상으로 대규모 무역 회사를 위한 웹 애플리케이션을 만들고 있습니다. 모든 사용자가 편리하게 사용할 수 있도록 해야 합니다. 아이디어는 다음과 같습니다. 러시아어를 사용하는 독자는 러시아어로 데이터를 볼 수 있는 기회를 가져야 하며 미국인에게는 자료를 영어로, 스페인 사람에게는 스페인어로(예기치 않게 그렇죠?) 자료를 읽는 것이 더 편리할 것입니다.
![다국어 애플리케이션 구현 - 2]()
오늘은
여러 국제화 모델을 고려하겠습니다 . 그 중 하나(제가 가장 좋아하는 모델입니다. :) Java의 구현을 살펴보겠습니다. 오늘 우리는 기니피그로서 영화 데이터 보드를 갖게 될 것입니다. 너무 왜곡하지 말자. 그러면 연설자가 많지 않을 것이다. 예를 들어, 괜찮습니다. 그리고 우리는 영화의 이름(감독 - 엑스트라의 경우)을 번역할 것입니다:
1. 언어별 번역표
이 모델의 핵심은 각 언어마다 데이터베이스에 번역이 필요한 모든 셀이 포함된 별도의 테이블이 있다는 것입니다. 이 방법의 단점은 새 언어를 추가할 때마다 새 테이블을 추가해야 한다는 것입니다. 즉, 우리 고객이 아주 잘하고 있고 세계 여러 나라(사실상 언어)로 응용 프로그램을 확장하고 있다고 상상해 봅시다. 이는 혀당 하나의 정제를 추가해야 함을 의미합니다. 결과적으로 우리는 절반 또는 거의 전부가 보조 번역 테이블로 구성된 데이터베이스를 갖게 됩니다.
영화 자체의 개략도:
번역 테이블:
2. 모두를 위한 하나
특정 모델에 속하는 각 테이블에는 언어판 식별자가 포함된 필드가 추가됩니다. 따라서 데이터베이스에는 번역과 함께 이 테이블도 포함됩니다. 문제는 하나의 개체가 여러 번역(언어)에 해당할 수 있다는 것입니다. 결과적으로 엔터티의 중복이 발생하게 되며 이는 논리를 크게 혼란시키고 복잡하게 만들어 좋지 않습니다.
UML을 살펴봅니다:
테이블 동영상:
테이블 언어:
3. 혀당 컬럼
테이블의 각 언어에 대한 각 열에 대해 별도의 번역 열이 생성됩니다. 이 접근 방식의 단점은 또 다시 많은 수의 언어가 추가되면 데이터베이스 구조를 매번 변경해야 한다는 점이며 이는 나쁜 접근 방식으로 간주됩니다. 또한 국제화를 요구하는 표시가 얼마나 부풀려질지 상상해 보십시오. 지원되는 언어의 수를 미리 알고 있고, 너무 많지 않으며, 각 모델이 모든 언어 변형에 존재해야 하는 모델을 고려해 볼 가치가 있습니다.
UML:
모든 항목을 포함하는 테이블:
4. 외부 번역
이 옵션은 외부 도구(Google 번역, Bing 번역 등)를 연결하여 구현됩니다. 최대한 많은 방문자에게 정보를 제공해야 할 때 사용하는데, 이런 정보가 많습니다. 너무 많아요. 이 경우 모든 언어의 정보를 데이터베이스에 직접 저장하지 않고 동적으로 번역하도록 결정할 수 있습니다. 그러나 기계 번역의 품질이 좋지 않은 경우가 많다는 점을 기억할 가치가 있습니다. 이 옵션은 매우 경제적이라고만 간주될 수 있습니다(각 출판물을 번역할 자원이 없는 경우). 올바른 번역에 있어서 흔히 발생하는 문제는 언어를 잘 모르는 번역가가 단어의 잘못된 의미를 선택하여 사용자를 혼란스럽게 하여 사용자가 버튼에 적힌 내용의 의미를 스스로 파악하도록 강요한다는 것입니다. 문장을 정확하게 번역하는 것뿐만 아니라 그 의미를 특정 언어와 국적에 맞게 전달하는 것도 중요합니다. 개발자는 일부 언어에서 성별에 대해 많은 문제를 안고 있습니다. 사용자의 성별에 따라 코드의 구문을 복제해야 하며, 명사에도 성별이 있을 뿐만 아니라 형용사와 동사에도 다르게 어형이 있다는 점을 고려해야 합니다. 응용 프로그램에서 영어 이외의 언어를 선택한 경우 선택한 언어의 단어와 함께 아직 번역되지 않은 요소가 있는 경우가 있습니다. 여러 언어가 표시되고 모든 것이 뒤섞여 사용자가 애플리케이션을 이해할 수 없는 일종의 바빌론으로 판명되면 더욱 악화됩니다. 예:
https://cloud.google.com/translate/
5. 애플리케이션 수준 지원 파일
번역을 저장하기 위해 별도의 파일이 생성됩니다. 1개당 파일 1개 또는 정제 1개당 1개 파일(미세 분쇄)일 수 있습니다. 이 옵션은 이러한 파일에 많은 텍스트를 저장할 수 있기 때문에 자주 사용됩니다. 즉, 테이블과 데이터베이스 자체가 비대해지지 않습니다. 또한 이러한 필드에 대해 데이터베이스를 두드릴 필요가 없으며 코드에서 요청된 언어에 따라 파일을 동적으로 교체할 수 있다는 점도 편리합니다. 결과적으로 파일은 우리에게 사전 역할을 하며 여기서 키는 언어이고 값은 텍스트입니다. 그러나 아래의 ".properties" 형식에만 국한되지 않으며 이러한 파일 형식은 JSON, XML 등 매우 다양할 수 있습니다. 단점은 이 경우 데이터베이스의 정규화가 크게 감소한다는 것입니다. 또한 데이터 무결성은 더 이상 데이터베이스에만 의존하지 않고 직렬화 메커니즘에도 의존합니다.
이 주제에 대한 훌륭한 기사 번역이 포함된 사전 파일의 예:
6. 각 테이블에 대한 보조 번역 테이블
제 생각에는 가장 유연한 솔루션입니다. 이 접근 방식의 핵심은 언어에 대한 별도의 테이블을 만드는 것입니다. 해당 테이블에 대한 번역 가능성을 구현해야 하는 경우 언어 테이블과 함께 링크가 생성되고 링크 테이블에는 언어 ID, 요소 ID 및 번역이 있는 열이 포함됩니다. 들리는 것만큼 무섭지는 않습니다. 이 접근 방식을 사용하면 지원되는 언어를 상당히 유연하게 확장할 수 있습니다. 좀 더 자세히 살펴보겠습니다.
UML:
영화가 포함된 테이블:
언어 테이블:
번역 테이블: ![다국어 애플리케이션 구현 - 21]()
그리고 위에서 말했듯이 Java 코드의 옵션 중 하나의 구현을 살펴보겠습니다(아시다시피 이것이 마지막 옵션이 될 것입니다). 애플리케이션 자체에는 그런 것이 없습니다. 컨트롤러에서 dao 레이어로 이동하겠습니다. 우리는 create 메소드를 살펴볼 것입니다 - 예를 들어 이것으로 충분할 것입니다. 그럼 가자)) 우리의 본질은 영화입니다.
@Builder
@Getter
public class Movie {
private Long id;
private String producer;
}
흥미롭지는 않습니다. 단지 첫 번째 테이블의 모델을 구현하는 것뿐입니다.
dto(Data Transfer Object) 변환기가 있는 컨트롤러:
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/cities")
public class MovieController {
private final MovieService movieService;
@PostMapping
public ResponseEntity<moviedto> create(MovieDTO movieDTO) {
return new ResponseEntity<>(toDTO(movieService.create(fromDTO(movieDTO), movieDTO.getNameTranslations()), movieDTO.getNameTranslations()), HttpStatus.CREATED);
}
private Movie fromDTO(MovieDTO dto) {
return Movie.builder()
.id(dto.getId())
.producer(dto.getProducer())
.build();
}
private MovieDTO toDTO(Movie movie, Map<string, string=""> nameTranslation) {
return MovieDTO.builder()
.id(movie.getId())
.producer(movie.getProducer())
.nameTranslations(nameTranslation)
.build();
}
}
DTO에서는 번역을 맵으로 전달하고, 키는 언어 약어이고, 값은 번역 값(영화 이름)입니다.
DTO:
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String producer;
@JsonProperty("nameTranslations")
private Map<String, String> nameTranslations;
}
위에서 설명한 것처럼 dto 클래스 자체, 번역을 위한 맵, 나머지 필드는 Movie 모델의 표시입니다.
영화 서비스로 넘어가겠습니다.
public interface MovieService {
Movie create(Movie movie, Map nameList);
}
구현:
@Service
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService {
private final MovieDAO movieDAO;
private LanguageService languageService;
@Override
public Movie create(Movie movie, Map<string, string=""> nameList) {
movieDAO.create(movie);
Map<Long, String> map = new HashMap<>();
nameList.forEach((x, y) -> map.put(languageService.getIdByLangCode(x), y));
movieDAO.createTranslator(movie.getId(), map);
return movie;
}
}
여기서는 약어로 언어 ID를 검색하기 위해 상대적으로 타사 LanguageService 서비스를 사용하는 것을 볼 수 있습니다. 그리고 이 식별자를 사용하여 번역(지도 형식으로도)을 연결 테이블에 저장합니다.
DAO를 살펴보겠습니다.
public interface MovieDAO {
void create(Movie movie);
void createTranslator(Long movieId, Map<Long,String> nameTranslations);
}
구현:
@RequiredArgsConstructor
@Repository
public class MovieDAOImpl implements MovieDAO {
private final JdbcTemplate jdbcTemplate;
private static final String CREATE_MOVIE = "INSERT INTO movies(id, producer) VALUES(?, ?)";
private static final String CREATE_TRANSLATOR = "INSERT INTO movies_translator(movies_id, language_id, name) VALUES(?, ?, ?)";
@Override
public void create(Movie movie) {
jdbcTemplate.update(CREATE_MOVIE, movie.getId(), movie.getProducer());
}
@Override
public void createTranslator(Long movieId, Map<Long, String> nameTranslations) {
nameTranslations.forEach((x, y) -> jdbcTemplate.update(CREATE_TRANSLATOR, movieId, x, y));
}
}
여기서 우리는 그것에 대한 본질과 언어(사전)의 보존을 봅니다. 그리고 그렇습니다. 여기서는 Spring JDBC가 사용됩니다. 더 투명하기 때문에 초보자에게 더 적합하다고 생각합니다. "타사" 서비스로 넘어가겠습니다.
언어 서비스:
public interface LanguageService {
Long getIdByLangCode(String lang);
}
구현:
@Service
@RequiredArgsConstructor
public class LanguageServiceImpl implements LanguageService {
private final LanguageDAO languageDAO;
@Override
public Long getIdByLangCode(String lang) {
return languageDAO.getIdByLangCode(lang);
}
}
특별한 내용은 없습니다. 단축명으로 검색하세요.
다오:
public interface LanguageDAO {
Long getIdByLangCode(String lang);
}
구현:
@RequiredArgsConstructor
@Repository
public class LanguageDAOImpl implements LanguageDAO {
private final JdbcTemplate jdbcTemplate;
private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
@Override
public Long getIdByLangCode(String lang) {
return jdbcTemplate.queryForObject(FIND_ID_BY_LANG_CODE, Long.class, lang);
}
}
구조: ![다국어 애플리케이션 구현 - 23]()
위에서 설명한 모든 모델은 생명권을 갖습니다. 상황에 따라 어떤 것을 사용할지 결정해야 합니다. 물론 이것이 전부는 아닙니다. 다양한 언어에 대해 다양한 데이터베이스 사용, 캐시, 다양한 프레임워크 사용 등을 포함하여 더 다양한 접근 방식이 있습니다. 오늘은 그게 전부이고...
GO TO FULL VERSION