diff --git a/pom.xml b/pom.xml
index 5ab62c717..0b07cc732 100644
--- a/pom.xml
+++ b/pom.xml
@@ -553,6 +553,11 @@
lzstring4java
0.1
+
+ software.amazon.awssdk
+ translate
+ 2.40.15
+
UTF-8
diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java
index 6b3b243b0..4aa29178a 100644
--- a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java
+++ b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIController.java
@@ -400,6 +400,7 @@ protected HashMap getAuthorProjectConfig(Authentication auth,
config.put("projectBaseURL", projectBaseURL);
config.put("previewProjectURL", contextPath + "/preview/unit/" + project.getId());
config.put("chatGptEnabled", !StringUtils.isEmpty(appProperties.getProperty("OPENAI_API_KEY")));
+ config.put("translationServiceEnabled", this.awsPropertiesConfigured());
config.put("cRaterRequestURL", contextPath + "/api/c-rater");
config.put("importStepsURL",
contextPath + "/api/author/project/importSteps/" + project.getId());
@@ -424,6 +425,12 @@ protected HashMap getAuthorProjectConfig(Authentication auth,
return config;
}
+ private boolean awsPropertiesConfigured() {
+ return !(StringUtils.isEmpty(appProperties.getProperty("aws.accessKeyId"))
+ || StringUtils.isEmpty(appProperties.getProperty("aws.secretAccessKey"))
+ || StringUtils.isEmpty(appProperties.getProperty("aws.region")));
+ }
+
/**
* Get the run that uses the project id
*
diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslatableText.java b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslatableText.java
new file mode 100644
index 000000000..2fc8270ff
--- /dev/null
+++ b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslatableText.java
@@ -0,0 +1,32 @@
+package org.wise.portal.presentation.web.controllers.author.project;
+
+import lombok.Getter;
+
+@Getter
+public class TranslatableText {
+ private String srcLangCode;
+ private String targetLangCode;
+ private String srcText;
+
+ public TranslatableText(String srcLang, String targetLang, String srcText) {
+ this.srcLangCode = this.convertLanguageToAWSCode(srcLang);
+ this.targetLangCode = this.convertLanguageToAWSCode(targetLang);
+ this.srcText = srcText;
+ }
+
+ private String convertLanguageToAWSCode(String language) throws IllegalArgumentException {
+ return switch (language) {
+ case "English" -> "en";
+ case "Spanish" -> "es-MX";
+ case "Italian" -> "it";
+ case "Japanese" -> "ja";
+ case "German" -> "de";
+ case "Chinese (Simplified)" -> "zh";
+ case "Chinese (Traditional)" -> "zh-TW";
+ case "Dutch" -> "nl";
+ case "Korean" -> "ko";
+ case "Vietnamese" -> "vi";
+ default -> throw new IllegalArgumentException("Invalid language provided");
+ };
+ }
+}
diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslateProjectAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslateProjectAPIController.java
index c21b3d509..e2cbe8f25 100644
--- a/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslateProjectAPIController.java
+++ b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslateProjectAPIController.java
@@ -1,15 +1,18 @@
package org.wise.portal.presentation.web.controllers.author.project;
import java.io.IOException;
+
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.Authentication;
-import org.springframework.stereotype.Controller;
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.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
import org.wise.portal.domain.project.impl.ProjectImpl;
import org.wise.portal.domain.user.User;
import org.wise.portal.service.project.ProjectService;
@@ -18,7 +21,14 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
-@Controller
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.translate.TranslateClient;
+import software.amazon.awssdk.services.translate.model.TranslateTextRequest;
+import software.amazon.awssdk.services.translate.model.TranslateTextResponse;
+
+@RestController
@RequestMapping("/api/author/project/translate")
@Secured({ "ROLE_AUTHOR" })
public class TranslateProjectAPIController {
@@ -32,8 +42,16 @@ public class TranslateProjectAPIController {
@Autowired
protected TranslateProjectService translateProjectService;
+ @Value("${aws.accessKeyId:}")
+ private String accessKey;
+
+ @Value("${aws.secretAccessKey:}")
+ private String secretKey;
+
+ @Value("${aws.region:}")
+ private String region;
+
@PostMapping("{projectId}/{locale}")
- @ResponseBody
protected void saveTranslations(Authentication auth,
@PathVariable("projectId") ProjectImpl project, @PathVariable("locale") String locale,
@RequestBody ObjectNode translations) throws IOException {
@@ -42,4 +60,48 @@ protected void saveTranslations(Authentication auth,
translateProjectService.saveTranslations(project, locale, translations.toString());
}
}
+
+ @PostMapping("suggest")
+ protected String getSuggestedTranslation(Authentication auth, @RequestBody TranslatableText translatableText)
+ throws IOException, IllegalArgumentException, ResponseStatusException {
+ if (accessKey.equals("") || secretKey.equals("") || region.equals("")) {
+ throw new ResponseStatusException(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ "Missing application properties necessary for AWS Translate"
+ );
+ } else {
+ TranslateClient translateClient = buildTranslateClient();
+ TranslateTextRequest request = buildTranslateTextRequest(translatableText);
+ return this.translateText(translateClient, request);
+ }
+ }
+
+ private TranslateClient buildTranslateClient() {
+ AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
+ return TranslateClient.builder()
+ .region(Region.of(region))
+ .credentialsProvider(StaticCredentialsProvider.create(credentials))
+ .build();
+ }
+
+ private TranslateTextRequest buildTranslateTextRequest(TranslatableText translatableText) {
+ return TranslateTextRequest.builder()
+ .text(translatableText.getSrcText())
+ .sourceLanguageCode(translatableText.getSrcLangCode())
+ .targetLanguageCode(translatableText.getTargetLangCode())
+ .build();
+ }
+
+ private String translateText(TranslateClient client, TranslateTextRequest request) throws ResponseStatusException {
+ TranslateTextResponse textResponse;
+ try {
+ textResponse = client.translateText(request);
+ } catch (Exception e) {
+ throw new ResponseStatusException(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ "Translation failed"
+ );
+ }
+ return textResponse.translatedText();
+ }
}
diff --git a/src/main/resources/application-dockerdev-sample.properties b/src/main/resources/application-dockerdev-sample.properties
index d3b2b0a73..c27db814b 100644
--- a/src/main/resources/application-dockerdev-sample.properties
+++ b/src/main/resources/application-dockerdev-sample.properties
@@ -42,6 +42,9 @@ spring.servlet.multipart.max-request-size=100MB
# henry_verification_url - [url or leave empty] henry verification url
# henry_scoring_url - [url or leave empty] henry scoring url
# henry_client_id - [id or leave empty] henry client token id
+# aws.accessKeyId - [key or leave empty] AWS Translate public key
+# aws.secretAccessKey - [key or leave empty] AWS Translate secret key
+# aws.region - [region or leave empty] AWS Translate server region
wise.name=My Local WISE Development Instance
wise.hostname=http://localhost:81
@@ -73,6 +76,10 @@ berkeley_cRater_scoring_url=
berkeley_cRater_client_id=
berkeley_cRater_password=
+aws.accessKeyId=
+aws.secretAccessKey=
+aws.region=
+
######### database properties #########
# Modify below as needed.
diff --git a/src/main/resources/application_sample.properties b/src/main/resources/application_sample.properties
index cba99ec23..f8baa0db9 100644
--- a/src/main/resources/application_sample.properties
+++ b/src/main/resources/application_sample.properties
@@ -42,6 +42,9 @@ spring.servlet.multipart.max-request-size=100MB
# henry_verification_url - [url or leave empty] henry verification url
# henry_scoring_url - [url or leave empty] henry scoring url
# henry_client_id - [id or leave empty] henry client token id
+# aws.accessKeyId - [key or leave empty] AWS Translate public key
+# aws.secretAccessKey - [key or leave empty] AWS Translate secret key
+# aws.region - [region or leave empty] AWS Translate server region
wise.name=My Local WISE Development Instance
wise.hostname=http://localhost:8080
@@ -73,6 +76,10 @@ berkeley_cRater_scoring_url=
berkeley_cRater_client_id=
berkeley_cRater_password=
+aws.accessKeyId=
+aws.secretAccessKey=
+aws.region=
+
######### database properties #########
# Modify below as needed.
diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java
index 042fddeaa..fa766eddf 100644
--- a/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java
+++ b/src/test/java/org/wise/portal/presentation/web/controllers/author/project/AuthorAPIControllerTest.java
@@ -81,6 +81,9 @@ public void getAuthorProjectConfig_HasProjectRun_ReturnCanGradeStudentWork() thr
expect(appProperties.getProperty("curriculum_base_www"))
.andReturn("http://localhost:8080/curriculum");
expect(appProperties.getProperty("OPENAI_API_KEY")).andReturn("OPENAPIKEY");
+ expect(appProperties.getProperty("aws.accessKeyId")).andReturn("ACCESSKEY");
+ expect(appProperties.getProperty("aws.secretAccessKey")).andReturn("SECRETKEY");
+ expect(appProperties.getProperty("aws.region")).andReturn("us-west-1");
replay(appProperties);
Map config = authorAPIController.getAuthorProjectConfig(teacherAuth, request,
project1);