From 2ea381183e06918c8532addfdcc71df23c3c8724 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Thu, 19 Feb 2026 22:12:10 -0500 Subject: [PATCH 01/11] Created API endpoint for ai translations --- pom.xml | 5 ++ .../TranslateProjectAPIController.java | 70 +++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 314cf5200..93f8b1f22 100644 --- a/pom.xml +++ b/pom.xml @@ -557,6 +557,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/TranslateProjectAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslateProjectAPIController.java index c21b3d509..197337970 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 @@ -2,14 +2,14 @@ import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; 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.wise.portal.domain.project.impl.ProjectImpl; import org.wise.portal.domain.user.User; import org.wise.portal.service.project.ProjectService; @@ -18,7 +18,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 +39,16 @@ public class TranslateProjectAPIController { @Autowired protected TranslateProjectService translateProjectService; + @Value("${aws.accessKeyId}") + private String accessKey; + + @Value("${aws.secretAccessKey}") + private String secretKey; + + @Value("${aws.region}") + private Region 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 +57,51 @@ protected void saveTranslations(Authentication auth, translateProjectService.saveTranslations(project, locale, translations.toString()); } } + + @PostMapping("translationSuggestions") + protected String getSuggestedTranslation(Authentication auth, @RequestBody ObjectNode objectNode) throws IOException, IllegalArgumentException { + String srcLang = objectNode.get("srcLang").asText(); + String targetLang = objectNode.get("targetLang").asText(); + String srcText = objectNode.get("srcText").asText(); + String srcLangCode = this.convertLanguageToAWSCode(srcLang); + String targetLangCode = this.convertLanguageToAWSCode(targetLang); + TranslateClient translateClient = buildTranslateClient(); + TranslateTextRequest request = buildTranslateTextRequest(srcText, srcLangCode, targetLangCode); + TranslateTextResponse textResponse = translateClient.translateText(request); + return textResponse.translatedText(); + } + + private TranslateClient buildTranslateClient() { + AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey); + return TranslateClient.builder() + .region(region) + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .build(); + } + + private TranslateTextRequest buildTranslateTextRequest(String srcText, String srcLangCode, + String targetLangCode) { + return TranslateTextRequest.builder() + .text(srcText) + .sourceLanguageCode(srcLangCode) + .targetLanguageCode(targetLangCode) + .build(); + } + + private String convertLanguageToAWSCode(String language) throws IllegalArgumentException { + return switch (language) { + case "English" -> "en"; + case "Spanish" -> "es"; + 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"); + }; + } + } From 54e2d652077ff750c725e9cb2cd2b52e01f6685f Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 20 Feb 2026 19:34:05 -0500 Subject: [PATCH 02/11] Translate with Mexican Spanish instead of basic Spanish --- .../author/project/TranslateProjectAPIController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 197337970..bcc2f7cec 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 @@ -91,7 +91,7 @@ private TranslateTextRequest buildTranslateTextRequest(String srcText, String sr private String convertLanguageToAWSCode(String language) throws IllegalArgumentException { return switch (language) { case "English" -> "en"; - case "Spanish" -> "es"; + case "Spanish" -> "es-MX"; case "Italian" -> "it"; case "Japanese" -> "ja"; case "German" -> "de"; From 28def20cb38fa00b8479656b1a0a3791883e95ab Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 13 Mar 2026 14:55:38 -0400 Subject: [PATCH 03/11] Updated sample app props --- src/main/resources/application-dockerdev-sample.properties | 7 +++++++ src/main/resources/application_sample.properties | 7 +++++++ 2 files changed, 14 insertions(+) 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. From a262a332151d27b31da35fb92d0c1b8e48055da0 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 13 Mar 2026 15:02:19 -0400 Subject: [PATCH 04/11] Handle missing app props, created TranslatableText class, and other suggested changes --- .../author/project/TranslatableText.java | 32 ++++++++++ .../TranslateProjectAPIController.java | 63 ++++++++----------- 2 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslatableText.java 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 bcc2f7cec..fe2bc205e 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,8 +1,10 @@ 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.web.bind.annotation.PathVariable; @@ -10,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; 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; @@ -39,14 +42,14 @@ public class TranslateProjectAPIController { @Autowired protected TranslateProjectService translateProjectService; - @Value("${aws.accessKeyId}") + @Value("${aws.accessKeyId:}") private String accessKey; - @Value("${aws.secretAccessKey}") + @Value("${aws.secretAccessKey:}") private String secretKey; - @Value("${aws.region}") - private Region region; + @Value("${aws.region:}") + private String region; @PostMapping("{projectId}/{locale}") protected void saveTranslations(Authentication auth, @@ -58,50 +61,34 @@ protected void saveTranslations(Authentication auth, } } - @PostMapping("translationSuggestions") - protected String getSuggestedTranslation(Authentication auth, @RequestBody ObjectNode objectNode) throws IOException, IllegalArgumentException { - String srcLang = objectNode.get("srcLang").asText(); - String targetLang = objectNode.get("targetLang").asText(); - String srcText = objectNode.get("srcText").asText(); - String srcLangCode = this.convertLanguageToAWSCode(srcLang); - String targetLangCode = this.convertLanguageToAWSCode(targetLang); - TranslateClient translateClient = buildTranslateClient(); - TranslateTextRequest request = buildTranslateTextRequest(srcText, srcLangCode, targetLangCode); - TranslateTextResponse textResponse = translateClient.translateText(request); - return textResponse.translatedText(); + @PostMapping("suggest") + protected String getSuggestedTranslation(Authentication auth, @RequestBody TranslatableText translatableText) throws IOException, IllegalArgumentException { + 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); + TranslateTextResponse textResponse = translateClient.translateText(request); + return textResponse.translatedText(); + } } private TranslateClient buildTranslateClient() { AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey); return TranslateClient.builder() - .region(region) + .region(Region.of(region)) .credentialsProvider(StaticCredentialsProvider.create(credentials)) .build(); } - private TranslateTextRequest buildTranslateTextRequest(String srcText, String srcLangCode, - String targetLangCode) { + private TranslateTextRequest buildTranslateTextRequest(TranslatableText translatableText) { return TranslateTextRequest.builder() - .text(srcText) - .sourceLanguageCode(srcLangCode) - .targetLanguageCode(targetLangCode) + .text(translatableText.getSrcText()) + .sourceLanguageCode(translatableText.getSrcLangCode()) + .targetLanguageCode(translatableText.getTargetLangCode()) .build(); } - - 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"); - }; - } - } From 52591c70624fdedf8487d4f80cf928999072cf29 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 13 Mar 2026 20:02:34 -0400 Subject: [PATCH 05/11] Throw server error if translation fails --- .../TranslateProjectAPIController.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) 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 fe2bc205e..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 @@ -62,7 +62,8 @@ protected void saveTranslations(Authentication auth, } @PostMapping("suggest") - protected String getSuggestedTranslation(Authentication auth, @RequestBody TranslatableText translatableText) throws IOException, IllegalArgumentException { + 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, @@ -71,8 +72,7 @@ protected String getSuggestedTranslation(Authentication auth, @RequestBody Trans } else { TranslateClient translateClient = buildTranslateClient(); TranslateTextRequest request = buildTranslateTextRequest(translatableText); - TranslateTextResponse textResponse = translateClient.translateText(request); - return textResponse.translatedText(); + return this.translateText(translateClient, request); } } @@ -91,4 +91,17 @@ private TranslateTextRequest buildTranslateTextRequest(TranslatableText translat .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(); + } } From 8a982d81552ba8a48a079d6f1a0890e1c2bd534f Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 13 Mar 2026 20:03:52 -0400 Subject: [PATCH 06/11] Add translationServiceEnabled field to config depending on application properties --- .../controllers/author/project/AuthorAPIController.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 * From 7c93790fa84455e9895f91a0acfbbf8f004c22d5 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Mon, 16 Mar 2026 17:44:28 -0400 Subject: [PATCH 07/11] Fixed failing test --- .../controllers/author/project/AuthorAPIControllerTest.java | 3 +++ 1 file changed, 3 insertions(+) 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); From 23136b0556192693628cb2af96cec437a2f23106 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Fri, 20 Mar 2026 09:51:32 -0400 Subject: [PATCH 08/11] Moved AWS keys to bottom of properties files --- .../application-dockerdev-sample.properties | 7 +++++++ src/main/resources/application_sample.properties | 14 +++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/resources/application-dockerdev-sample.properties b/src/main/resources/application-dockerdev-sample.properties index c27db814b..adebb1469 100644 --- a/src/main/resources/application-dockerdev-sample.properties +++ b/src/main/resources/application-dockerdev-sample.properties @@ -223,6 +223,13 @@ system-wide-salt=secret #speech-to-text.aws.region= #speech-to-text.aws.identity-pool-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 +aws.accessKeyId= +aws.secretAccessKey= +aws.region= + # OpenAI and AWS Bedrock Chat endpoints (optional) #OPENAI_API_KEY= #aws.bedrock.api.key= diff --git a/src/main/resources/application_sample.properties b/src/main/resources/application_sample.properties index f8baa0db9..29bac3956 100644 --- a/src/main/resources/application_sample.properties +++ b/src/main/resources/application_sample.properties @@ -42,9 +42,6 @@ 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 @@ -76,10 +73,6 @@ berkeley_cRater_scoring_url= berkeley_cRater_client_id= berkeley_cRater_password= -aws.accessKeyId= -aws.secretAccessKey= -aws.region= - ######### database properties ######### # Modify below as needed. @@ -223,6 +216,13 @@ system-wide-salt=secret #speech-to-text.aws.region= #speech-to-text.aws.identity-pool-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 +aws.accessKeyId= +aws.secretAccessKey= +aws.region= + # OpenAI and AWS Bedrock Chat endpoints (optional) #OPENAI_API_KEY= #aws.bedrock.api.key= From 6e331f6ab54c78483da3be3f21ee6a99390d4f1e Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Mon, 23 Mar 2026 21:45:48 -0400 Subject: [PATCH 09/11] Split translation suggestion into separate controller --- .settings/org.eclipse.jdt.core.prefs | 16 ++-- .../author/project/TranslatableText.java | 6 +- .../TranslateProjectAPIController.java | 67 +--------------- .../TranslationSuggestionAPIController.java | 79 +++++++++++++++++++ ...ranslationSuggestionAPIControllerTest.java | 31 ++++++++ 5 files changed, 123 insertions(+), 76 deletions(-) create mode 100644 src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIController.java create mode 100644 src/test/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIControllerTest.java diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index b451da941..010d2f80c 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 @@ -49,14 +49,14 @@ org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignor org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning @@ -70,7 +70,7 @@ org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled 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 index 2fc8270ff..b77090b3a 100644 --- 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 @@ -4,9 +4,9 @@ @Getter public class TranslatableText { - private String srcLangCode; - private String targetLangCode; - private String srcText; + protected String srcLangCode; + protected String targetLangCode; + protected String srcText; public TranslatableText(String srcLang, String targetLang, String srcText) { this.srcLangCode = this.convertLanguageToAWSCode(srcLang); 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 e2cbe8f25..a7952b8e5 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 @@ -3,8 +3,6 @@ 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.web.bind.annotation.PathVariable; @@ -12,7 +10,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; 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; @@ -21,15 +18,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; -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") +@RequestMapping("/api/author/project/translate/{projectId}/{locale}") @Secured({ "ROLE_AUTHOR" }) public class TranslateProjectAPIController { @@ -42,16 +32,7 @@ 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}") + @PostMapping protected void saveTranslations(Authentication auth, @PathVariable("projectId") ProjectImpl project, @PathVariable("locale") String locale, @RequestBody ObjectNode translations) throws IOException { @@ -60,48 +41,4 @@ 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/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIController.java new file mode 100644 index 000000000..08d76ac9e --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIController.java @@ -0,0 +1,79 @@ +package org.wise.portal.presentation.web.controllers.author.project; + +import java.io.IOException; + +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.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 org.springframework.web.server.ResponseStatusException; + +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/suggest") +@Secured({ "ROLE_AUTHOR" }) +public class TranslationSuggestionAPIController { + + @Value("${aws.accessKeyId:}") + private String accessKey; + + @Value("${aws.secretAccessKey:}") + private String secretKey; + + @Value("${aws.region:}") + private String region; + + @PostMapping + 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/test/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIControllerTest.java new file mode 100644 index 000000000..8a99d888a --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/author/project/TranslationSuggestionAPIControllerTest.java @@ -0,0 +1,31 @@ +package org.wise.portal.presentation.web.controllers.author.project; + +import org.easymock.EasyMockExtension; +import org.easymock.TestSubject; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.server.ResponseStatusException; +import org.wise.portal.presentation.web.controllers.APIControllerTest; + +@ExtendWith(EasyMockExtension.class) +public class TranslationSuggestionAPIControllerTest extends APIControllerTest { + + @TestSubject + private final TranslationSuggestionAPIController controller = new TranslationSuggestionAPIController(); + + @Test + public void getSuggestedTranslation_ThrowIfPropertiesEmpty() throws Exception { + ReflectionTestUtils.setField(controller, "accessKey", ""); + ReflectionTestUtils.setField(controller, "secretKey", ""); + ReflectionTestUtils.setField(controller, "region", ""); + + TranslatableText tt = new TranslatableText("English", "Spanish", "text to translate"); + + assertThrows(ResponseStatusException.class, () -> { + controller.getSuggestedTranslation(teacherAuth, tt); + }); + } +} + From ef7ff466d17e5e9c2f4ba9f8412725f77596a71c Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Mon, 23 Mar 2026 21:53:54 -0400 Subject: [PATCH 10/11] Forgot to remove duplicate aws properties in dockerdev sample --- src/main/resources/application-dockerdev-sample.properties | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/resources/application-dockerdev-sample.properties b/src/main/resources/application-dockerdev-sample.properties index adebb1469..dd8dde2dd 100644 --- a/src/main/resources/application-dockerdev-sample.properties +++ b/src/main/resources/application-dockerdev-sample.properties @@ -42,9 +42,6 @@ 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 @@ -76,10 +73,6 @@ berkeley_cRater_scoring_url= berkeley_cRater_client_id= berkeley_cRater_password= -aws.accessKeyId= -aws.secretAccessKey= -aws.region= - ######### database properties ######### # Modify below as needed. From 982c2749137f15c618a2c725850e483038aa7ff5 Mon Sep 17 00:00:00 2001 From: Aaron Detre Date: Mon, 23 Mar 2026 22:10:41 -0400 Subject: [PATCH 11/11] Reverted automatically changed file --- .settings/org.eclipse.jdt.core.prefs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 010d2f80c..b451da941 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.annotation.nonnull=javax.annotation.Nonnull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=javax.annotation.ParametersAreNonnullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=javax.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 @@ -49,14 +49,14 @@ org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignor org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=warning org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore -org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning @@ -70,7 +70,7 @@ org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled