From 39949375d4a623e15fff27e1696a300c159efdb0 Mon Sep 17 00:00:00 2001 From: Jean Aurambault Date: Thu, 13 Jun 2024 23:33:34 -0700 Subject: [PATCH] WIP Add support for Plural String import initial version of the connector could only push/pull (maybe buggy), this is an attempt at supporting push translations too --- .../ImportLocalizedAssetCommandTest.java | 54 ++++++++ .../expected/res/values-fr-rCA/strings.xml | 25 ++++ .../expected/res/values-fr-rFR/strings.xml | 25 ++++ .../expected/res/values-ja-rJP/strings.xml | 22 ++++ .../expected/res/values-ru-rRU/strings.xml | 31 +++++ .../input/source/res/values/strings.xml | 25 ++++ .../res/values-fr-rCA/strings.xml | 4 + .../res/values-fr-rFR/strings.xml | 25 ++++ .../res/values-ja-rJP/strings.xml | 22 ++++ .../res/values-ru-rRU/strings.xml | 31 +++++ .../box/l10n/mojito/LocaleMappingHelper.java | 3 +- .../strings/AndroidStringDocumentMapper.java | 50 ++++---- .../thirdparty/ThirdPartyTMSPhrase.java | 120 ++++++++++++++++-- .../thirdparty/phrase/PhraseClient.java | 2 +- .../AndroidStringDocumentMapperTest.java | 6 +- 15 files changed, 404 insertions(+), 41 deletions(-) create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rCA/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rFR/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ja-rJP/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ru-rRU/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/source/res/values/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rCA/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rFR/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ja-rJP/strings.xml create mode 100644 cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ru-rRU/strings.xml diff --git a/cli/src/test/java/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest.java b/cli/src/test/java/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest.java index ed34a12b57..06a9c81541 100644 --- a/cli/src/test/java/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest.java +++ b/cli/src/test/java/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest.java @@ -4,10 +4,12 @@ import com.box.l10n.mojito.entity.Locale; import com.box.l10n.mojito.entity.Repository; import com.box.l10n.mojito.service.locale.LocaleService; +import org.junit.Assume; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; /** * @author jeanaurambault @@ -19,6 +21,10 @@ public class ImportLocalizedAssetCommandTest extends CLITestBase { @Autowired LocaleService localeService; + // TODO(ja) move in its own class + @Value("${test.phrase-client.projectId}") + String testProjectId; + @Test public void importAndroidStrings() throws Exception { @@ -92,6 +98,54 @@ public void importAndroidStringsPlural() throws Exception { checkExpectedGeneratedResources(); } + @Test + public void importAndroidStringsPluralWithThirdPartySync() throws Exception { + Assume.assumeNotNull(testProjectId); + + Repository repository = createTestRepoUsingRepoService(); + repositoryService.addRepositoryLocale(repository, "ru-RU"); + + getL10nJCommander() + .run( + "push", + "-r", + repository.getName(), + "-s", + getInputResourcesTestDir("source").getAbsolutePath()); + + getL10nJCommander() + .run( + "import", + "-r", + repository.getName(), + "-s", + getInputResourcesTestDir("source").getAbsolutePath(), + "-t", + getInputResourcesTestDir("translations").getAbsolutePath()); + + getL10nJCommander() + .run( + "pull", + "-r", + repository.getName(), + "-s", + getInputResourcesTestDir("source").getAbsolutePath(), + "-t", + getTargetTestDir().getAbsolutePath()); + + getL10nJCommander() + .run( + "thirdparty-sync", + "-r", + repository.getName(), + "-p", + testProjectId, + "-a", + "PUSH,MAP_TEXTUNIT,PUSH_TRANSLATION,PULL"); + + checkExpectedGeneratedResources(); + } + @Test public void importMacStrings() throws Exception { diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rCA/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000000..bf62334b5b --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rCA/strings.xml @@ -0,0 +1,25 @@ + + + + + %1$,d heure à cuisiner + %1$,d heures à cuisiner + + + + Membres + + + %1$,d collaborateur + %1$,d collaborateurs + + + + + %1$d ingrédient + %1$d ingrédients + + + + Supprimer + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rFR/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rFR/strings.xml new file mode 100644 index 0000000000..bf62334b5b --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-fr-rFR/strings.xml @@ -0,0 +1,25 @@ + + + + + %1$,d heure à cuisiner + %1$,d heures à cuisiner + + + + Membres + + + %1$,d collaborateur + %1$,d collaborateurs + + + + + %1$d ingrédient + %1$d ingrédients + + + + Supprimer + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ja-rJP/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ja-rJP/strings.xml new file mode 100644 index 0000000000..9091b03269 --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ja-rJP/strings.xml @@ -0,0 +1,22 @@ + + + + + 所要時間:%1$,d 時間 + + + + ユーザー + + + ボード参加者:%1$,d 人 + + + + + %1$d 個の材料 + + + + 削除 + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ru-rRU/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ru-rRU/strings.xml new file mode 100644 index 0000000000..0df87a4dfc --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/expected/res/values-ru-rRU/strings.xml @@ -0,0 +1,31 @@ + + + + + Приготовление: %1$,d час + Приготовление: %1$,d часа + Приготовление: %1$,d часов + Приготовление: %1$,d часа + + + + Люди + + + %1$,d соавтор + %1$,d соавтора + %1$,d соавторов + %1$,d соавтора + + + + + %1$d ингредиент + %1$d ингредиента + %1$d ингредиентов + %1$d ингредиента + + + + Удалить + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/source/res/values/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/source/res/values/strings.xml new file mode 100644 index 0000000000..df1703cc80 --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/source/res/values/strings.xml @@ -0,0 +1,25 @@ + + + + + %,d hr to cook + %,d hrs to cook + + + + People + + + %1$,d collaborator + %1$,d collaborators + + + + + %d ingredient + %d ingredients + + + + Delete + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rCA/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000000..0f85d2dd1e --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rCA/strings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rFR/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rFR/strings.xml new file mode 100644 index 0000000000..0623031bdd --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-fr-rFR/strings.xml @@ -0,0 +1,25 @@ + + + + + %1$,d heure à cuisiner + %1$,d heures à cuisiner + + + + Membres + + + %1$,d collaborateur + %1$,d collaborateurs + + + + + %1$d ingrédient + %1$d ingrédients + + + + Supprimer + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ja-rJP/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ja-rJP/strings.xml new file mode 100644 index 0000000000..22a1c39bf1 --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ja-rJP/strings.xml @@ -0,0 +1,22 @@ + + + + + 所要時間:%1$,d 時間 + + + + ユーザー + + + ボード参加者:%1$,d 人 + + + + + %1$d 個の材料 + + + + 削除 + \ No newline at end of file diff --git a/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ru-rRU/strings.xml b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ru-rRU/strings.xml new file mode 100644 index 0000000000..0df87a4dfc --- /dev/null +++ b/cli/src/test/resources/com/box/l10n/mojito/cli/command/ImportLocalizedAssetCommandTest_IO/importAndroidStringsPluralWithThirdPartySync/input/translations/res/values-ru-rRU/strings.xml @@ -0,0 +1,31 @@ + + + + + Приготовление: %1$,d час + Приготовление: %1$,d часа + Приготовление: %1$,d часов + Приготовление: %1$,d часа + + + + Люди + + + %1$,d соавтор + %1$,d соавтора + %1$,d соавторов + %1$,d соавтора + + + + + %1$d ингредиент + %1$d ингредиента + %1$d ингредиентов + %1$d ингредиента + + + + Удалить + \ No newline at end of file diff --git a/common/src/main/java/com/box/l10n/mojito/LocaleMappingHelper.java b/common/src/main/java/com/box/l10n/mojito/LocaleMappingHelper.java index 08b3507c18..3b78325577 100644 --- a/common/src/main/java/com/box/l10n/mojito/LocaleMappingHelper.java +++ b/common/src/main/java/com/box/l10n/mojito/LocaleMappingHelper.java @@ -28,7 +28,8 @@ public Map getLocaleMapping(String localeMapppingParam) { * Gets the inverse locale mapping given the locale mapping param * * @param localeMapppingParam locale mapping param coming from the CLI - * @return A map containing the inverse locale mapping (key: the tag in the repository, value: file output tag) + * @return A map containing the inverse locale mapping (key: the tag in the repository, value: + * file output tag) */ public Map getInverseLocaleMapping(String localeMapppingParam) { diff --git a/webapp/src/main/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapper.java b/webapp/src/main/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapper.java index 7d94a450c2..f3a5b22142 100644 --- a/webapp/src/main/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapper.java +++ b/webapp/src/main/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapper.java @@ -9,12 +9,12 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,13 +34,15 @@ public class AndroidStringDocumentMapper { private final String repositoryName; private final PluralNameParser pluralNameParser; private final boolean addTextUnitIdInName; + private final Map pluralFormToCommaId; public AndroidStringDocumentMapper( String pluralSeparator, String assetDelimiter, String locale, String repositoryName, - boolean addTextUnitIdInName) { + boolean addTextUnitIdInName, + Map pluralFormToCommaId) { this.pluralSeparator = pluralSeparator; this.assetDelimiter = Optional.ofNullable(Strings.emptyToNull(assetDelimiter)).orElse(DEFAULT_ASSET_DELIMITER); @@ -48,11 +50,12 @@ public AndroidStringDocumentMapper( this.repositoryName = repositoryName; this.pluralNameParser = new PluralNameParser(); this.addTextUnitIdInName = addTextUnitIdInName; + this.pluralFormToCommaId = pluralFormToCommaId; } public AndroidStringDocumentMapper( String pluralSeparator, String assetDelimiter, String locale, String repositoryName) { - this(pluralSeparator, assetDelimiter, locale, repositoryName, false); + this(pluralSeparator, assetDelimiter, locale, repositoryName, false, null); } public AndroidStringDocumentMapper(String pluralSeparator, String assetDelimiter) { @@ -103,14 +106,18 @@ public AndroidStringDocument readFromTextUnits(List textUnits, bool pluralByOther.forEach( (pluralFormOther, builder) -> { if (addTextUnitIdInName) { - String ids = - builder.getSortedItems().stream() - .map(AndroidPluralItem::getId) - .map(Objects::toString) - .collect(Collectors.joining(",")); + String ids; + if (pluralFormToCommaId != null) { + ids = this.pluralFormToCommaId.get(pluralFormOther); + } else { + ids = + builder.getSortedItems().stream() + .map(AndroidPluralItem::getId) + .map(Objects::toString) + .collect(Collectors.joining(",")); + } builder.setName(ids + "#@#" + builder.getName()); } - document.addPlural(builder.build()); }); @@ -160,7 +167,7 @@ TextUnitDTO addTextUnitDTOAttributes(TextUnitDTO textUnit) { } AndroidPluralQuantity androidPluralQuantity = - AndroidPluralQuantity.valueOf(textUnit.getPluralForm()); + AndroidPluralQuantity.valueOf(textUnit.getPluralForm().toUpperCase(Locale.ROOT)); textUnit.setTmTextUnitId(ids.get(androidPluralQuantity.ordinal())); } textUnit.setAssetPath(nameParts[1]); @@ -230,7 +237,7 @@ boolean isSingularTextUnit(TextUnitDTO textUnit) { return Strings.isNullOrEmpty(textUnit.getPluralForm()); } - String getKeyToGroupByPluralOtherAndComment(TextUnitDTO textUnit) { + public static String getKeyToGroupByPluralOtherAndComment(TextUnitDTO textUnit) { return textUnit.getAssetPath() + DEFAULT_ASSET_DELIMITER + textUnit.getPluralFormOther() @@ -268,24 +275,21 @@ static String removeInvalidControlCharacter(String str) { return withoutControlCharacters; } - /** - * should use {@link com.box.l10n.mojito.okapi.filters.AndroidFilter#unescape(String)} - */ + /** should use {@link com.box.l10n.mojito.okapi.filters.AndroidFilter#unescape(String)} */ static String unescape(String str) { String unescape = str; - if (StringUtils.startsWith(unescape, "\"") - && StringUtils.endsWith(unescape, "\"")) { - unescape = - unescape.substring(1, unescape.length() - 1); + if (StringUtils.startsWith(unescape, "\"") && StringUtils.endsWith(unescape, "\"")) { + unescape = unescape.substring(1, unescape.length() - 1); } - unescape = Strings.nullToEmpty(unescape) - .replaceAll("\\\\'", "'") - .replaceAll("\\\\\"", "\"") - .replaceAll("\\\\@", "@") - .replaceAll("\\\\n", "\n"); + unescape = + Strings.nullToEmpty(unescape) + .replaceAll("\\\\'", "'") + .replaceAll("\\\\\"", "\"") + .replaceAll("\\\\@", "@") + .replaceAll("\\\\n", "\n"); return unescape; } diff --git a/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/ThirdPartyTMSPhrase.java b/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/ThirdPartyTMSPhrase.java index 8322ca3276..400539493f 100644 --- a/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/ThirdPartyTMSPhrase.java +++ b/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/ThirdPartyTMSPhrase.java @@ -8,10 +8,12 @@ import com.box.l10n.mojito.entity.PollableTask; import com.box.l10n.mojito.entity.Repository; import com.box.l10n.mojito.entity.RepositoryLocale; +import com.box.l10n.mojito.json.ObjectMapper; import com.box.l10n.mojito.service.pollableTask.PollableFuture; import com.box.l10n.mojito.service.repository.RepositoryService; import com.box.l10n.mojito.service.thirdparty.phrase.PhraseClient; import com.box.l10n.mojito.service.tm.importer.TextUnitBatchImporterService; +import com.box.l10n.mojito.service.tm.search.SearchType; import com.box.l10n.mojito.service.tm.search.StatusFilter; import com.box.l10n.mojito.service.tm.search.TextUnitDTO; import com.box.l10n.mojito.service.tm.search.TextUnitSearcher; @@ -22,12 +24,16 @@ import com.phrase.client.model.TranslationKey; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -121,7 +127,7 @@ public void push( List search = getSourceTextUnitDTOs(repository, skipTextUnitsWithPattern, skipAssetsWithPathPattern); - String text = getFileContent(pluralSeparator, search, true); + String text = getFileContent(pluralSeparator, search, true, null); String tagForUpload = getTagForUpload(); phraseClient.uploadAndWait( @@ -161,6 +167,24 @@ private List getSourceTextUnitDTOs( return textUnitSearcher.search(parameters); } + private List getSourceTextUnitDTOsPluralOnly( + Repository repository, String skipTextUnitsWithPattern, String skipAssetsWithPathPattern) { + + TextUnitSearcherParameters parameters = new TextUnitSearcherParameters(); + + parameters.setRepositoryIds(repository.getId()); + parameters.setForRootLocale(true); + parameters.setDoNotTranslateFilter(false); + parameters.setUsedFilter(UsedFilter.USED); + parameters.setSkipTextUnitWithPattern(skipTextUnitsWithPattern); + parameters.setSkipAssetPathWithPattern(skipAssetsWithPathPattern); + parameters.setSearchType(SearchType.ILIKE); + parameters.setPluralFormsFiltered(false); + parameters.setPluralFormOther("%"); + + return textUnitSearcher.search(parameters); + } + public static String getTagForUpload() { ZonedDateTime zonedDateTime = JSR310Migration.dateTimeNowInUTC(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd_HH_mm_ss_SSS"); @@ -191,9 +215,11 @@ public PollableFuture pull( logger.info("Downloading locale: {} from Phrase", localeTag); String fileContent = phraseClient.localeDownload(projectId, localeTag, "xml"); + logger.info("file content from pull: {}", fileContent); + AndroidStringDocumentMapper mapper = new AndroidStringDocumentMapper( - pluralSeparator, null, localeTag, repository.getName(), true); + pluralSeparator, null, localeTag, repository.getName(), true, null); List textUnitDTOS = mapper.mapToTextUnits(AndroidStringDocumentReader.fromText(fileContent)); @@ -215,6 +241,36 @@ public void pushTranslations( String includeTextUnitsWithPattern, List optionList) { + List pluralTextUnitDTOs = + getSourceTextUnitDTOsPluralOnly( + repository, skipTextUnitsWithPattern, skipAssetsWithPathPattern); + + Map> pluralFormOtherToTextUnitDTO = + pluralTextUnitDTOs.stream() + .collect( + Collectors.groupingBy( + AndroidStringDocumentMapper::getKeyToGroupByPluralOtherAndComment)); + + pluralFormOtherToTextUnitDTO.forEach( + (key, value) -> { + if (value.size() != 6) { + throw new RuntimeException("there must be only 6 text units per PluralFormOther value"); + } + }); + + Map pluralFormToCommaId = + pluralFormOtherToTextUnitDTO.entrySet().stream() + .map( + e -> + new SimpleEntry<>( + e.getKey(), + e.getValue().stream() + .sorted(new ByPluralFormComparator()) + .map(TextUnitDTO::getTmTextUnitId) + .map(String::valueOf) + .collect(Collectors.joining(",")))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Set repositoryLocalesWithoutRootLocale = repositoryService.getRepositoryLocalesWithoutRootLocale(repository); @@ -227,15 +283,29 @@ public void pushTranslations( includeTextUnitsWithPattern, repositoryLocale); - String fileContent = getFileContent(pluralSeparator, textUnitDTOS, false); + if (textUnitDTOS.isEmpty()) { + logger.info("Not translation, skip upload"); + } else { - phraseClient.uploadCreateFile( - projectId, - repositoryLocale.getLocale().getBcp47Tag(), - "xml", - repository.getName() + "-strings.xml", - fileContent, - null); + logger.info("Print text unit for {}", repositoryLocale.getLocale().getBcp47Tag()); + textUnitDTOS.forEach( + textUnitDTO -> + logger.info( + "Textunit: {}", + ObjectMapper.withIndentedOutput().writeValueAsStringUnchecked(textUnitDTO))); + + String fileContent = + getFileContent(pluralSeparator, textUnitDTOS, false, pluralFormToCommaId); + logger.info("Push translation to phrase:\n{}", fileContent); + + phraseClient.uploadAndWait( + projectId, + repositoryLocale.getLocale().getBcp47Tag(), + "xml", + repository.getName() + "-strings.xml", + fileContent, + null); + } } } @@ -254,15 +324,19 @@ private List getTextUnitDTOSForLocale( parameters.setSkipTextUnitWithPattern(skipTextUnitsWithPattern); parameters.setSkipAssetPathWithPattern(skipAssetsWithPathPattern); parameters.setIncludeTextUnitsWithPattern(includeTextUnitsWithPattern); - parameters.setPluralFormsFiltered(true); // TODO(ja) plural string won't import because the ID won't match? + parameters.setPluralFormsFiltered(true); return textUnitSearcher.search(parameters); } private static String getFileContent( - String pluralSeparator, List textUnitDTOS, boolean useSource) { + String pluralSeparator, + List textUnitDTOS, + boolean useSource, + Map pluralFormToCommaId) { AndroidStringDocumentMapper androidStringDocumentMapper = - new AndroidStringDocumentMapper(pluralSeparator, null, null, null, true); + new AndroidStringDocumentMapper( + pluralSeparator, null, null, null, true, pluralFormToCommaId); AndroidStringDocument androidStringDocument = androidStringDocumentMapper.readFromTextUnits(textUnitDTOS, useSource); @@ -278,4 +352,24 @@ public void pullSource( Map localeMapping) { throw new UnsupportedOperationException("Pull source is not supported"); } + + static class ByPluralFormComparator implements Comparator { + + private final Map orderMap; + + public ByPluralFormComparator() { + this.orderMap = new HashMap<>(); + int i = 0; + for (String v : Arrays.asList("zero", "one", "two", "few", "many", "other")) { + this.orderMap.put(v, i++); + } + } + + @Override + public int compare(TextUnitDTO o1, TextUnitDTO o2) { + return Integer.compare( + orderMap.getOrDefault(o1.getPluralForm(), -1), + orderMap.getOrDefault(o2.getPluralForm(), -1)); + } + } } diff --git a/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/phrase/PhraseClient.java b/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/phrase/PhraseClient.java index 7557ca3a99..6acc3c824e 100644 --- a/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/phrase/PhraseClient.java +++ b/webapp/src/main/java/com/box/l10n/mojito/service/thirdparty/phrase/PhraseClient.java @@ -93,7 +93,7 @@ Upload waitForUploadToFinish(String projectId, String uploadId) { } } - public String uploadCreateFile( + String uploadCreateFile( String projectId, String localeId, String fileFormat, diff --git a/webapp/src/test/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapperTest.java b/webapp/src/test/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapperTest.java index f7b342ed71..4a8b96ba4b 100644 --- a/webapp/src/test/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapperTest.java +++ b/webapp/src/test/java/com/box/l10n/mojito/android/strings/AndroidStringDocumentMapperTest.java @@ -60,7 +60,7 @@ public void testReadFromSourceTextUnitsWithoutPluralForms() { @Test public void testReadFromSourceTextUnitsWithoutPluralFormsAndWithTmTextUnitIdInName() { - mapper = new AndroidStringDocumentMapper("_", assetDelimiter, null, null, true); + mapper = new AndroidStringDocumentMapper("_", assetDelimiter, null, null, true, null); textUnits.add(sourceTextUnitDTO(123L, "name0", "content0", "comment0", "my/path0", null, null)); textUnits.add(sourceTextUnitDTO(124L, "name1", "content1", "comment1", "my/path1", null, null)); @@ -159,7 +159,7 @@ public void testReadFromSourceTextUnitsWithPlurals() { @Test public void testReadFromSourceTextUnitsWithPluralsAndWithTmTextUnitIdInName() { - mapper = new AndroidStringDocumentMapper(" _", assetDelimiter, null, null, true); + mapper = new AndroidStringDocumentMapper(" _", assetDelimiter, null, null, true, null); textUnits.add(sourceTextUnitDTO(123L, "name0", "content0", "comment0", "my/path0", null, null)); textUnits.add( @@ -818,7 +818,7 @@ public void testAddTextUnitDTOAttributesAssetPathAndName() { @Test public void testAddTextUnitDTOAttributesTextUnitIdAndAssetPathAndName() { - mapper = new AndroidStringDocumentMapper("_", null, null, null, true); + mapper = new AndroidStringDocumentMapper("_", null, null, null, true, null); TextUnitDTO textUnitDTO = new TextUnitDTO(); textUnitDTO.setName("156151#@#asset_path#@#name_part1");