Skip to content

Commit 702f2a3

Browse files
authored
feat: validate MT languages (#904)
1 parent 4867d9e commit 702f2a3

File tree

7 files changed

+172
-13
lines changed

7 files changed

+172
-13
lines changed

src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.crowdin.client.labels.model.AddLabelRequest;
1010
import com.crowdin.client.labels.model.Label;
1111
import com.crowdin.client.languages.model.Language;
12+
import com.crowdin.client.machinetranslationengines.model.MachineTranslation;
1213
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
1314
import com.crowdin.client.projectsgroups.model.Project;
1415
import com.crowdin.client.projectsgroups.model.ProjectSettings;
@@ -520,6 +521,16 @@ public PreTranslationStatus checkPreTranslation(String preTranslationId) {
520521
.getData());
521522
}
522523

524+
@Override
525+
public MachineTranslation getMt(Long mtId) throws ResponseException {
526+
Map<BiPredicate<String, String>, ResponseException> errorHandler = new LinkedHashMap<>() {{
527+
put((code, message) -> code.equals("403") && message.contains("Endpoint isn't allowed for token scopes"),
528+
new ResponseException("Unable to retrieve MT due to insufficient token scopes"));
529+
}};
530+
return executeRequest(errorHandler, () -> this.client.getMachineTranslationEnginesApi().getMt(mtId))
531+
.getData();
532+
}
533+
523534
@Override
524535
public String getProjectUrl() {
525536
return this.getProject().getWebUrl();

src/main/java/com/crowdin/cli/client/ProjectClient.java

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.crowdin.client.labels.model.AddLabelRequest;
88
import com.crowdin.client.labels.model.Label;
99
import com.crowdin.client.languages.model.Language;
10+
import com.crowdin.client.machinetranslationengines.model.MachineTranslation;
1011
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
1112
import com.crowdin.client.projectsgroups.model.Project;
1213
import com.crowdin.client.sourcefiles.model.*;
@@ -127,6 +128,8 @@ default CrowdinProjectFull downloadFullProject() {
127128

128129
PreTranslationStatus checkPreTranslation(String preTranslationId);
129130

131+
MachineTranslation getMt(Long mtId) throws ResponseException;
132+
130133
String getProjectUrl();
131134

132135
List<? extends Project> listProjects();

src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java

+28-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.crowdin.cli.client.CrowdinProjectFull;
55
import com.crowdin.cli.client.CrowdinProjectInfo;
66
import com.crowdin.cli.client.ProjectClient;
7+
import com.crowdin.cli.client.ResponseException;
78
import com.crowdin.cli.commands.NewAction;
89
import com.crowdin.cli.commands.Outputter;
910
import com.crowdin.cli.commands.functionality.ProjectFilesUtils;
@@ -13,6 +14,7 @@
1314
import com.crowdin.cli.utils.console.ConsoleSpinner;
1415
import com.crowdin.client.labels.model.Label;
1516
import com.crowdin.client.languages.model.Language;
17+
import com.crowdin.client.machinetranslationengines.model.MachineTranslation;
1618
import com.crowdin.client.projectsgroups.model.Type;
1719
import com.crowdin.client.sourcefiles.model.Branch;
1820
import com.crowdin.client.sourcefiles.model.FileInfo;
@@ -24,6 +26,7 @@
2426
import java.util.stream.Collectors;
2527

2628
import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
29+
import static com.crowdin.cli.utils.console.ExecutionStatus.OK;
2730
import static com.crowdin.cli.utils.console.ExecutionStatus.WARNING;
2831

2932
@AllArgsConstructor
@@ -50,7 +53,7 @@ public void act(Outputter out, PropertiesWithFiles properties, ProjectClient cli
5053
this.noProgress, this.plainView, () -> client.downloadFullProject(this.branchName));
5154
boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
5255

53-
List<String> languages = this.prepareLanguageIds(project);
56+
List<String> languages = this.prepareLanguageIds(project, client, out);
5457
List<Long> labelIds = this.prepareLabelIds(out, client);
5558

5659
if (isStringsBasedProject) {
@@ -121,23 +124,35 @@ public void act(Outputter out, PropertiesWithFiles properties, ProjectClient cli
121124
}
122125
}
123126

124-
private List<String> prepareLanguageIds(CrowdinProjectInfo projectInfo) {
127+
private List<String> prepareLanguageIds(CrowdinProjectInfo projectInfo, ProjectClient client, Outputter out) {
125128
List<String> projectLanguages = projectInfo.getProjectLanguages(false).stream()
126129
.map(Language::getId)
127130
.collect(Collectors.toList());
128-
if (languageIds.size() == 1 && BaseCli.ALL.equalsIgnoreCase(languageIds.get(0))) {
129-
return projectLanguages;
130-
} else {
131-
String wrongLanguageIds = languageIds.stream()
132-
.filter(langId -> !projectLanguages.contains(langId))
133-
.map(id -> "'" + id + "'")
134-
.collect(Collectors.joining(", "));
135-
if (!wrongLanguageIds.isEmpty()) {
136-
throw new ExitCodeExceptionMapper.NotFoundException(
137-
String.format(RESOURCE_BUNDLE.getString("error.languages_not_exist"), wrongLanguageIds));
131+
if (languageIds == null || (languageIds.size() == 1 && BaseCli.ALL.equalsIgnoreCase(languageIds.get(0)))) {
132+
if (Method.MT.equals(method)) {
133+
try {
134+
ConsoleSpinner.start(out, RESOURCE_BUNDLE.getString("message.spinner.validating_mt_languages"), this.noProgress);
135+
MachineTranslation mt = client.getMt(engineId);
136+
ConsoleSpinner.stop(OK, RESOURCE_BUNDLE.getString("message.spinner.validation_success"));
137+
Set<String> supportedMtLanguageIds = new HashSet<>(mt.getSupportedLanguageIds());
138+
return projectLanguages.stream()
139+
.filter(supportedMtLanguageIds::contains)
140+
.collect(Collectors.toList());
141+
} catch (ResponseException e) {
142+
ConsoleSpinner.stop(WARNING, String.format(RESOURCE_BUNDLE.getString("message.spinner.validation_error"), e.getMessage()));
143+
}
138144
}
139-
return languageIds;
145+
return projectLanguages;
146+
}
147+
String wrongLanguageIds = languageIds.stream()
148+
.filter(langId -> !projectLanguages.contains(langId))
149+
.map(id -> "'" + id + "'")
150+
.collect(Collectors.joining(", "));
151+
if (!wrongLanguageIds.isEmpty()) {
152+
throw new ExitCodeExceptionMapper.NotFoundException(
153+
String.format(RESOURCE_BUNDLE.getString("error.languages_not_exist"), wrongLanguageIds));
140154
}
155+
return languageIds;
141156
}
142157

143158
private List<Long> prepareLabelIds(Outputter out, ProjectClient client) {

src/main/resources/messages/messages.properties

+2
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,7 @@ message.spinner.building_tm_percents=Building translation memory (%d%%)
851851
message.spinner.releasing_distribution=Releasing distribution
852852
message.spinner.releasing_distribution_percents=Releasing distribution (%d%%)
853853
message.spinner.validation_success=Validation was successful
854+
message.spinner.validation_error=Validation error: %s
854855
message.spinner.pre_translate=Pre-translation is running...
855856
message.spinner.pre_translate_percents=Pre-translation is completed by (%d%%)
856857
message.spinner.pre_translate_done=Pre-translation is finished (%d%%)
@@ -862,6 +863,7 @@ message.spinner.cloning_branch=Cloning branch
862863
message.spinner.merging_branch=Merging branch
863864
message.spinner.cloning_branch_percents=Cloning branch @|bold (%d%%)|@
864865
message.spinner.merging_branch_percents=Merging branch @|bold (%d%%)|@
866+
message.spinner.validating_mt_languages=Validating supported languages for the specified MT engine
865867

866868
message.faq_link=Visit the @|cyan https://crowdin.github.io/crowdin-cli/faq|@ for more details
867869
message.translations_build_unsuccessful=Didn't manage to build translations

src/test/java/com/crowdin/cli/client/CrowdinProjectClientTest.java

+16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.crowdin.client.core.model.PatchRequest;
1212
import com.crowdin.client.languages.model.LanguageResponseList;
1313
import com.crowdin.client.languages.model.LanguageResponseObject;
14+
import com.crowdin.client.machinetranslationengines.model.MachineTranslationResponseObject;
1415
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
1516
import com.crowdin.client.projectsgroups.model.Project;
1617
import com.crowdin.client.projectsgroups.model.ProjectResponseObject;
@@ -78,6 +79,7 @@ public class CrowdinProjectClientTest {
7879
private static final String languageId = "uk";
7980
private static final long buildId = 62;
8081
private static final long stringId = 52;
82+
private static final long mtId = 21;
8183
private static final String downloadUrl = "https://downloadme.crowdin.com";
8284
private static final String downloadUrlMalformed = "https";
8385

@@ -112,6 +114,8 @@ public class CrowdinProjectClientTest {
112114
String.format("%s/projects/%d/translations/builds/%d", url, projectId, buildId);
113115
private static final String downloadBuildUrl =
114116
String.format("%s/projects/%d/translations/builds/%d/download", url, projectId, buildId);
117+
private static final String getMtUrl =
118+
String.format("%s/mts/%d", url, mtId);
115119

116120
private static final String addSourceStringUrl =
117121
String.format("%s/projects/%d/strings", url, projectId);
@@ -469,6 +473,18 @@ public void testCheckBuildingTranslation() {
469473
verifyNoMoreInteractions(httpClientMock);
470474
}
471475

476+
@Test
477+
public void testGetMt() throws ResponseException {
478+
MachineTranslationResponseObject response = new MachineTranslationResponseObject();
479+
when(httpClientMock.get(eq(getMtUrl), any(), eq(MachineTranslationResponseObject.class)))
480+
.thenReturn(response);
481+
482+
client.getMt(mtId);
483+
484+
verify(httpClientMock).get(eq(getMtUrl), any(), eq(MachineTranslationResponseObject.class));
485+
verifyNoMoreInteractions(httpClientMock);
486+
}
487+
472488
@Test
473489
public void testDownloadBuild() {
474490
DownloadLinkResponseObject response = new DownloadLinkResponseObject() {{

src/test/java/com/crowdin/cli/commands/actions/PreTranslateActionTest.java

+64
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
import com.crowdin.cli.client.CrowdinProjectFull;
44
import com.crowdin.cli.client.ProjectBuilder;
55
import com.crowdin.cli.client.ProjectClient;
6+
import com.crowdin.cli.client.ResponseException;
67
import com.crowdin.cli.commands.Outputter;
78
import com.crowdin.cli.properties.NewPropertiesWithFilesUtilBuilder;
89
import com.crowdin.cli.properties.PropertiesWithFiles;
910
import com.crowdin.cli.properties.helper.FileHelperTest;
1011
import com.crowdin.cli.properties.helper.TempProject;
1112
import com.crowdin.cli.utils.Utils;
1213
import com.crowdin.client.labels.model.Label;
14+
import com.crowdin.client.machinetranslationengines.model.MachineTranslation;
1315
import com.crowdin.client.projectsgroups.model.Type;
1416
import com.crowdin.client.translations.model.ApplyPreTranslationRequest;
1517
import com.crowdin.client.translations.model.ApplyPreTranslationStringsBasedRequest;
18+
import com.crowdin.client.translations.model.Method;
1619
import com.crowdin.client.translations.model.PreTranslationStatus;
1720
import org.junit.jupiter.api.AfterEach;
1821
import org.junit.jupiter.api.BeforeEach;
@@ -166,4 +169,65 @@ public void testPreTranslate_StringsBased() {
166169
verify(client).startPreTranslationStringsBased(eq(request));
167170
verifyNoMoreInteractions(client);
168171
}
172+
173+
@Test
174+
public void testPreTranslate_MtMethodNoLanguages() throws ResponseException {
175+
String fileName = "first.po";
176+
String labelName = "label_1";
177+
String branchName = "main";
178+
NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
179+
.minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
180+
.setBasePath(project.getBasePath());
181+
PropertiesWithFiles pb = pbBuilder.build();
182+
ProjectBuilder projectBuilder = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
183+
.addFile(fileName, "gettext", 101L, null, 81L)
184+
.addBranches(81L, branchName);
185+
CrowdinProjectFull crowdinProjectFull = projectBuilder.build();
186+
crowdinProjectFull.setType(Type.FILES_BASED);
187+
188+
Label label1 = new Label() {{
189+
setId(91L);
190+
setTitle(labelName);
191+
}};
192+
List<Label> labels = List.of(label1);
193+
194+
ApplyPreTranslationRequest request = new ApplyPreTranslationRequest() {{
195+
setLabelIds(List.of(91L));
196+
setMethod(Method.MT);
197+
setEngineId(1L);
198+
setFileIds(List.of(101L));
199+
setLanguageIds(List.of("ua"));
200+
}};
201+
PreTranslationStatus preTransInProgress = new PreTranslationStatus() {{
202+
setStatus("progress");
203+
setProgress(10);
204+
setIdentifier("121");
205+
}};
206+
PreTranslationStatus preTransFinished = new PreTranslationStatus() {{
207+
setStatus("finished");
208+
setIdentifier("121");
209+
}};
210+
211+
ProjectClient client = mock(ProjectClient.class);
212+
MachineTranslation mt = new MachineTranslation() {{
213+
setSupportedLanguageIds(List.of("ua", "fr"));
214+
}};
215+
216+
when(client.downloadFullProject(eq(branchName))).thenReturn(crowdinProjectFull);
217+
when(client.getMt(1L)).thenReturn(mt);
218+
when(client.startPreTranslation(eq(request))).thenReturn(preTransInProgress);
219+
when(client.checkPreTranslation("121")).thenReturn(preTransFinished);
220+
when(client.listLabels()).thenReturn(labels);
221+
222+
PreTranslateAction action = new PreTranslateAction(null, List.of(fileName), Method.MT, 1L, branchName, null,
223+
null, null, null, null, false, false, List.of(labelName), null);
224+
action.act(Outputter.getDefault(), pb, client);
225+
226+
verify(client).downloadFullProject(eq(branchName));
227+
verify(client).listLabels();
228+
verify(client).getMt(eq(1L));
229+
verify(client).startPreTranslation(eq(request));
230+
verify(client).checkPreTranslation(eq("121"));
231+
verifyNoMoreInteractions(client);
232+
}
169233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.crowdin.cli.commands.picocli;
2+
3+
import com.crowdin.client.distributions.model.ExportMode;
4+
import org.hamcrest.Matchers;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.Arguments;
8+
import org.junit.jupiter.params.provider.MethodSource;
9+
10+
import java.util.Arrays;
11+
import java.util.Collections;
12+
import java.util.List;
13+
import java.util.stream.Stream;
14+
15+
import static com.crowdin.cli.commands.picocli.GenericCommand.RESOURCE_BUNDLE;
16+
import static org.hamcrest.MatcherAssert.assertThat;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.params.provider.Arguments.arguments;
19+
20+
public class DistributionEditSubcommandTest extends PicocliTestUtils {
21+
22+
@Test
23+
public void testDistributionEditInvalidOptions() {
24+
this.executeInvalidParams(CommandNames.DISTRIBUTION, CommandNames.EDIT, "hash");
25+
}
26+
27+
@ParameterizedTest
28+
@MethodSource
29+
public void testSubCommandCheckValidOptions(String hash, String name, ExportMode exportMode, List<String> files, List<Integer> bundleIds) {
30+
DistributionEditSubcommand distributionEditSubcommand = new DistributionEditSubcommand();
31+
distributionEditSubcommand.hash = hash;
32+
distributionEditSubcommand.name = name;
33+
distributionEditSubcommand.exportMode = exportMode;
34+
distributionEditSubcommand.files = files;
35+
distributionEditSubcommand.bundleIds = bundleIds;
36+
37+
List<String> errors = distributionEditSubcommand.checkOptions();
38+
assertEquals(Collections.emptyList(), errors);
39+
}
40+
41+
public static Stream<Arguments> testSubCommandCheckValidOptions() {
42+
return Stream.of(
43+
arguments("hash0", null, null, Arrays.asList("strings.xml", "strings2.xml"), null),
44+
arguments("hash1", null, ExportMode.DEFAULT, null, null),
45+
arguments("hash2", null, ExportMode.BUNDLE, null, Arrays.asList(1l, 2l)),
46+
arguments("hash3", "Distribution1", null, null, null));
47+
}
48+
}

0 commit comments

Comments
 (0)