Skip to content

Commit

Permalink
Merge pull request #55 from goalSetter09/10th-Kampus-BE-49
Browse files Browse the repository at this point in the history
[FEATURE] 번역 기능 구현
  • Loading branch information
u-genuine authored Jan 28, 2025
2 parents 61d3525 + 9d0fb73 commit 70f27c1
Show file tree
Hide file tree
Showing 21 changed files with 430 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public ResponseEntity<DataResponse<SignupResponse>> signup(
@RequestBody SignupRequest request) {
return ResponseEntity.ok(DataResponse.from(SignupResponse.of(
authService.signup(request.email(), request.uniqueId(), request.providerId(), request.username(),
request.nickname(), request.nationality()))));
request.nickname(), request.nationality(), request.languageCode()))));
}

@Operation(summary = "서버 헬스 체크", description = "서버 헬스 체크")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ public class AuthService {
private final UserAppender userAppender;

public Long signup(String email, String uniqueId, String providerId, String username,
String nickname, String nationality) {
String nickname, String nationality, String languageCode) {
return userAppender.appendUser(
email,
uniqueId,
providerId,
username,
nickname,
nationality
nationality,
languageCode
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public record SignupRequest(
String providerId,
String username,
String nickname,
String nationality
String nationality,
String languageCode
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ public class PostScrapValidator {
private final PostAuthorResolver postAuthorResolver;

public void validatePostScrap(Long postId, Long userId){
Long authorId = postAuthorResolver.getAuthorId(postId);

// 본인 게시글 또는 이미 스크랩한 게시글은 예외처리
if(userId.equals(authorId)){
if(postAuthorResolver.validatePostAuthor(postId, userId)){
throw new AppException(ErrorCode.POST_SCRAP_FORBIDDEN);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.cotato.kampus.domain.translation.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cotato.kampus.domain.translation.application.TranslationService;
import com.cotato.kampus.domain.translation.dto.request.PostTranslationRequest;
import com.cotato.kampus.domain.translation.dto.request.TextTranslationRequest;
import com.cotato.kampus.domain.translation.dto.response.PostTranslationResponse;
import com.cotato.kampus.domain.translation.dto.response.TextTranslationResponse;
import com.cotato.kampus.global.common.dto.DataResponse;
import com.deepl.api.DeepLException;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Tag(name = "번역 API", description = "번역 API")
@RestController
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@RequestMapping("/v1/api/translations")
public class TranslationController {

private final TranslationService translationService;

@PostMapping("{postId}")
@Operation(summary = "게시글 번역", description = "게시글 id를 통해 게시글을 번역합니다.")
public ResponseEntity<DataResponse<PostTranslationResponse>> translatePost(@PathVariable Long postId) throws
DeepLException,
InterruptedException {
return ResponseEntity.ok(DataResponse.from(
PostTranslationResponse.from(
translationService.translatePost(postId)
)
)
);
}

@PostMapping("/posts")
@Operation(summary = "작성 게시글 번역", description = "작성중인 게시글을 번역합니다.")
public ResponseEntity<DataResponse<PostTranslationResponse>> translateWritingPost(
@RequestBody PostTranslationRequest request) throws
DeepLException,
InterruptedException {
return ResponseEntity.ok(DataResponse.from(
PostTranslationResponse.from(
translationService.translatePost(request.title(), request.content(), request.targetLanguageCode())
)
)
);
}

@PostMapping("/texts")
@Operation(summary = "단순 텍스트 번역", description = "사용자의 선호 언어를 기반으로 단순 텍스트를 번역합니다.")
public ResponseEntity<DataResponse<TextTranslationResponse>> translateText(
@RequestBody TextTranslationRequest request) throws
DeepLException, InterruptedException {
return ResponseEntity.ok(DataResponse.from(
TextTranslationResponse.from(
translationService.translateText(request.content())
)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.cotato.kampus.domain.translation.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.translation.domain.PostTranslation;
import com.cotato.kampus.domain.translation.dto.TranslatedPost;
import com.cotato.kampus.domain.translation.dto.TranslatedText;
import com.deepl.api.DeepLException;
import com.deepl.api.Translator;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

/*
게시글에서 사용되는 번역을 담당
*/
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class PostTranslator {
private final Translator translator;

public TranslatedPost translatePost(String title, String content, String languageCode) throws
DeepLException, InterruptedException {
PostTranslation postTranslation = PostTranslation.builder()
.title(title)
.content(content)
.targetLanguageCode(languageCode)
.build();
PostTranslation translated = postTranslation.translate(translator);
return TranslatedPost.of(translated.getTranslatedTitle(), translated.getTranslatedContent());
}

public TranslatedText translateText(String content, String languageCode) throws
DeepLException, InterruptedException {
return TranslatedText.from(
translator.translateText(content, null, languageCode).getText()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.cotato.kampus.domain.translation.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.domain.common.application.ApiUserResolver;
import com.cotato.kampus.domain.post.application.PostFinder;
import com.cotato.kampus.domain.post.dto.PostDto;
import com.cotato.kampus.domain.translation.dto.TranslatedPost;
import com.cotato.kampus.domain.translation.dto.TranslatedText;
import com.cotato.kampus.domain.user.enums.PreferredLanguage;
import com.deepl.api.DeepLException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class TranslationService {

private final PostTranslator postTranslator;
private final PostFinder postFinder;
private final ApiUserResolver apiUserResolver;
private final TranslationValidator translationValidator;

public TranslatedPost translatePost(Long postId) throws DeepLException, InterruptedException {
// 1. 게시글 조회
PostDto post = postFinder.findPost(postId);

// 2. 사용자의 선호 언어 조회
PreferredLanguage preferredLanguage = apiUserResolver.getUser().getPreferredLanguage();

// 3. 번역
return postTranslator.translatePost(post.title(), post.content(), preferredLanguage.getCode());
}

public TranslatedPost translatePost(String title, String content, String targetLanguageCode) throws
DeepLException, InterruptedException {
// 1. 번역할 게시글 검증
translationValidator.validatePost(title, content);

// 2. 번역
return postTranslator.translatePost(title, content, targetLanguageCode);
}

public TranslatedText translateText(String content) throws DeepLException, InterruptedException {
// 1. 번역할 텍스트 검증
translationValidator.validateText(content);

// 2. 사용자의 선호 언어 조회
PreferredLanguage preferredLanguage = apiUserResolver.getUser().getPreferredLanguage();

// 3. 번역
return postTranslator.translateText(content, preferredLanguage.getCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.cotato.kampus.domain.translation.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cotato.kampus.global.error.ErrorCode;
import com.cotato.kampus.global.error.exception.AppException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class TranslationValidator {

public void validatePost(String title, String content) throws AppException {
if (title == null || title.isEmpty()) {
throw new AppException(ErrorCode.INVALID_DEEPL_CONTENT);
}
if (content == null || content.isEmpty()) {
throw new AppException(ErrorCode.INVALID_DEEPL_CONTENT);
}
}

public void validateText(String content) {
if (content == null || content.isEmpty()) {
throw new AppException(ErrorCode.INVALID_DEEPL_CONTENT);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.cotato.kampus.domain.translation.domain;

import com.deepl.api.DeepLException;
import com.deepl.api.Translator;

import lombok.Builder;
import lombok.Getter;

@Getter
public class PostTranslation {

private String title;
private String content;
private String targetLanguageCode;
private String translatedTitle;
private String translatedContent;

@Builder
public PostTranslation(String title, String content, String targetLanguageCode) {
this.title = title;
this.content = content;
this.targetLanguageCode = targetLanguageCode;
}

public PostTranslation translate(Translator translator) throws DeepLException, InterruptedException {
translatedTitle = translator.translateText(title, null, targetLanguageCode).getText();
translatedContent = translator.translateText(content, null, targetLanguageCode).getText();
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.cotato.kampus.domain.translation.dto;

public record TranslatedPost(
String title,
String content
) {
public static TranslatedPost of(String title, String content) {
return new TranslatedPost(title, content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.cotato.kampus.domain.translation.dto;

public record TranslatedText(
String content
) {
public static TranslatedText from(String content) {
return new TranslatedText(content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.cotato.kampus.domain.translation.dto.request;

public record PostTranslationRequest(
String title,
String content,
String targetLanguageCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.cotato.kampus.domain.translation.dto.request;

public record TextTranslationRequest(
String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.cotato.kampus.domain.translation.dto.response;

import com.cotato.kampus.domain.translation.dto.TranslatedPost;

public record PostTranslationResponse(
String title,
String content
) {
public static PostTranslationResponse from(TranslatedPost translatedPost) {
return new PostTranslationResponse(
translatedPost.title(),
translatedPost.content()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.cotato.kampus.domain.translation.dto.response;

import com.cotato.kampus.domain.translation.dto.TranslatedText;

public record TextTranslationResponse(
String content
) {
public static TextTranslationResponse from(TranslatedText translatedText) {
return new TextTranslationResponse(translatedText.content());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.cotato.kampus.domain.user.dao.UserRepository;
import com.cotato.kampus.domain.user.domain.User;
import com.cotato.kampus.domain.user.enums.Nationality;
import com.cotato.kampus.domain.user.enums.PreferredLanguage;
import com.cotato.kampus.domain.user.enums.UserRole;

import lombok.AccessLevel;
Expand All @@ -22,14 +23,15 @@ public class UserAppender {

@Transactional
public Long appendUser(String email, String uniqueId, String providerId, String username,
String nickname, String nationality) {
String nickname, String nationality, String languageCode) {
User user = User.builder()
.username(username)
.uniqueId(uniqueId)
.providerId(providerId)
.email(email)
.nickname(nickname)
.nationality(Nationality.valueOf(nationality))
.preferredLanguage(PreferredLanguage.fromCode(languageCode))
.userRole(UserRole.UNVERIFIED)
.build();
log.info("user uniqueId: {}", user.getUniqueId());
Expand Down
Loading

0 comments on commit 70f27c1

Please sign in to comment.