diff --git a/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java b/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java index 83f2ebdf3..e22987139 100644 --- a/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java +++ b/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java @@ -29,10 +29,12 @@ import com.crowdin.client.sourcefiles.model.Branch; import com.crowdin.client.sourcefiles.model.ExportOptions; import com.crowdin.client.sourcefiles.model.FileInfo; +import com.crowdin.client.sourcefiles.model.GeneralFileExportOptions; import com.crowdin.client.sourcefiles.model.ImportOptions; import com.crowdin.client.sourcefiles.model.OtherFileImportOptions; import com.crowdin.client.sourcefiles.model.PropertyFileExportOptions; import com.crowdin.client.sourcefiles.model.SpreadsheetFileImportOptions; +import com.crowdin.client.sourcefiles.model.JavaScriptFileExportOptions; import com.crowdin.client.sourcefiles.model.UpdateFileRequest; import com.crowdin.client.sourcefiles.model.XmlFileImportOptions; import org.apache.commons.io.FilenameUtils; @@ -53,6 +55,8 @@ import static com.crowdin.cli.utils.console.ExecutionStatus.OK; import static com.crowdin.cli.utils.console.ExecutionStatus.SKIPPED; import static com.crowdin.cli.utils.console.ExecutionStatus.WARNING; +import static com.crowdin.client.sourcefiles.model.ExportQuotes.SINGLE; +import static com.crowdin.client.sourcefiles.model.ExportQuotes.DOUBLE; class UploadSourcesAction implements NewAction { @@ -350,21 +354,40 @@ private boolean isXml(java.io.File file) { } private ExportOptions buildExportOptions(java.io.File sourceFile, FileBean fileBean, String basePath) { - PropertyFileExportOptions exportOptions = new PropertyFileExportOptions(); String exportPattern = TranslationsUtils.replaceDoubleAsterisk( fileBean.getSource(), fileBean.getTranslation(), StringUtils.removeStart(sourceFile.getAbsolutePath(), basePath) ); + exportPattern = StringUtils.replacePattern(exportPattern, "[\\\\/]+", "/"); - exportOptions.setExportPattern(exportPattern); - exportOptions.setEscapeQuotes(fileBean.getEscapeQuotes()); - if (fileBean.getEscapeSpecialCharacters() != null) { - exportOptions.setEscapeSpecialCharacters(fileBean.getEscapeSpecialCharacters()); - } else if (SourcesUtils.isFileProperties(sourceFile)) { + + if (SourcesUtils.isFileProperties(sourceFile)) { + PropertyFileExportOptions exportOptions = new PropertyFileExportOptions(); + exportOptions.setExportPattern(exportPattern); + exportOptions.setEscapeQuotes(fileBean.getEscapeQuotes()); exportOptions.setEscapeSpecialCharacters(1); + + if (fileBean.getEscapeSpecialCharacters() != null) { + exportOptions.setEscapeSpecialCharacters(fileBean.getEscapeSpecialCharacters()); + } + + return exportOptions; + } else if (SourcesUtils.isFileJavaScript(sourceFile)) { + JavaScriptFileExportOptions exportOptions = new JavaScriptFileExportOptions(); + exportOptions.setExportPattern(exportPattern); + + if (fileBean.getExportQuotes() != null) { + exportOptions.setExportQuotes(fileBean.getExportQuotes().equals("double") ? DOUBLE : SINGLE); + } + + return exportOptions; + } else { + GeneralFileExportOptions exportOptions = new GeneralFileExportOptions(); + exportOptions.setExportPattern(exportPattern); + + return exportOptions; } - return exportOptions; } private Branch getOrCreateBranch(Outputter out, String branchName, ProjectClient client, CrowdinProjectFull project) { diff --git a/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java b/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java index 8259de10a..fd551ccbc 100644 --- a/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java +++ b/src/main/java/com/crowdin/cli/commands/functionality/SourcesUtils.java @@ -146,4 +146,8 @@ public static String getCommonPath(List sources, String basePath) { public static boolean isFileProperties(File source) { return FilenameUtils.isExtension(source.getName(), "properties"); } + + public static boolean isFileJavaScript(File source) { + return FilenameUtils.isExtension(source.getName(), "js"); + } } diff --git a/src/main/java/com/crowdin/cli/properties/FileBean.java b/src/main/java/com/crowdin/cli/properties/FileBean.java index bd8d9629e..d02886bc0 100755 --- a/src/main/java/com/crowdin/cli/properties/FileBean.java +++ b/src/main/java/com/crowdin/cli/properties/FileBean.java @@ -16,6 +16,7 @@ import static com.crowdin.cli.properties.PropertiesBuilder.DEST; import static com.crowdin.cli.properties.PropertiesBuilder.ESCAPE_QUOTES; import static com.crowdin.cli.properties.PropertiesBuilder.ESCAPE_SPECIAL_CHARACTERS; +import static com.crowdin.cli.properties.PropertiesBuilder.EXPORT_QUOTES; import static com.crowdin.cli.properties.PropertiesBuilder.EXCLUDED_TARGET_LANGUAGES; import static com.crowdin.cli.properties.PropertiesBuilder.EXPORT_APPROVED_ONLY; import static com.crowdin.cli.properties.PropertiesBuilder.FIRST_LINE_CONTAINS_HEADER; @@ -60,6 +61,7 @@ public class FileBean { private Boolean contentSegmentation; private Integer escapeQuotes; private Integer escapeSpecialCharacters; + private String exportQuotes; private Map translationReplace; private Boolean skipTranslatedOnly; private Boolean skipUntranslatedFiles; @@ -79,31 +81,33 @@ private FileBeanConfigurator() { @Override public FileBean buildFromMap(Map map) { FileBean fileBean = new FileBean(); - PropertiesBuilder.setPropertyIfExists(fileBean::setSource, map, SOURCE, String.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setDest, map, DEST, String.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setType, map, TYPE, String.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setTranslation, map, TRANSLATION, String.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setUpdateOption, map, UPDATE_OPTION, String.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setScheme, map, SCHEME, String.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setIgnore, map, IGNORE, List.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setTranslatableElements, map, TRANSLATABLE_ELEMENTS, List.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setLanguagesMapping, map, LANGUAGES_MAPPING, Map.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setTranslationReplace, map, TRANSLATION_REPLACE, Map.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setEscapeQuotes, map, ESCAPE_QUOTES, Integer.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setEscapeSpecialCharacters, map, ESCAPE_SPECIAL_CHARACTERS, Integer.class); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setFirstLineContainsHeader, map, FIRST_LINE_CONTAINS_HEADER); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setTranslateAttributes, map, TRANSLATE_ATTRIBUTES); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setTranslateContent, map, TRANSLATE_CONTENT); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setContentSegmentation, map, CONTENT_SEGMENTATION); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setMultilingualSpreadsheet, map, MULTILINGUAL_SPREADSHEET); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setSkipTranslatedOnly, map, SKIP_UNTRANSLATED_STRINGS); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setSkipUntranslatedFiles, map, SKIP_UNTRANSLATED_FILES); - PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setExportApprovedOnly, map, EXPORT_APPROVED_ONLY); + PropertiesBuilder.setPropertyIfExists(fileBean::setSource, map, SOURCE, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setDest, map, DEST, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setType, map, TYPE, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setTranslation, map, TRANSLATION, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setUpdateOption, map, UPDATE_OPTION, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setScheme, map, SCHEME, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setIgnore, map, IGNORE, List.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setTranslatableElements, map, TRANSLATABLE_ELEMENTS, List.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setLanguagesMapping, map, LANGUAGES_MAPPING, Map.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setTranslationReplace, map, TRANSLATION_REPLACE, Map.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setEscapeQuotes, map, ESCAPE_QUOTES, Integer.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setEscapeSpecialCharacters, map, ESCAPE_SPECIAL_CHARACTERS, Integer.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setExportQuotes, map, EXPORT_QUOTES, String.class); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setFirstLineContainsHeader, map, FIRST_LINE_CONTAINS_HEADER); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setTranslateAttributes, map, TRANSLATE_ATTRIBUTES); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setTranslateContent, map, TRANSLATE_CONTENT); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setContentSegmentation, map, CONTENT_SEGMENTATION); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setMultilingualSpreadsheet, map, MULTILINGUAL_SPREADSHEET); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setSkipTranslatedOnly, map, SKIP_UNTRANSLATED_STRINGS); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setSkipUntranslatedFiles, map, SKIP_UNTRANSLATED_FILES); + PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setExportApprovedOnly, map, EXPORT_APPROVED_ONLY); PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setExportStringsThatPassedWorkflow, map, EXPORT_STRINGS_THAT_PASSED_WORKFLOW); - PropertiesBuilder.setPropertyIfExists(fileBean::setLabels, map, LABELS, List.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setExcludedTargetLanguages, map, EXCLUDED_TARGET_LANGUAGES, List.class); - PropertiesBuilder.setPropertyIfExists(fileBean::setCustomSegmentation, map, CUSTOM_SEGMENTATION, String.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setLabels, map, LABELS, List.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setExcludedTargetLanguages, map, EXCLUDED_TARGET_LANGUAGES, List.class); + PropertiesBuilder.setPropertyIfExists(fileBean::setCustomSegmentation, map, CUSTOM_SEGMENTATION, String.class); PropertiesBuilder.setBooleanPropertyIfExists(fileBean::setImportTranslations, map, IMPORT_TRANSLATIONS); + return fileBean; } @@ -123,7 +127,6 @@ public void populateWithDefaultValues(FileBean bean) { } } - //Ignore if (bean.getIgnore() != null && !bean.getIgnore().isEmpty()) { List ignores = new ArrayList<>(); @@ -164,18 +167,22 @@ public void populateWithDefaultValues(FileBean bean) { @Override public List checkProperties(FileBean bean) { List errors = new ArrayList<>(); + if (StringUtils.isEmpty(bean.getSource())) { errors.add(RESOURCE_BUNDLE.getString("error.config.empty_source_section")); } + if (StringUtils.isEmpty(bean.getTranslation())) { errors.add(RESOURCE_BUNDLE.getString("error.config.empty_translation_section")); } else { if (!checkForDoubleAsterisks(bean.getSource(), bean.getTranslation())) { errors.add(RESOURCE_BUNDLE.getString("error.config.double_asterisk")); } + if (!PlaceholderUtil.containsLangPlaceholders(bean.getTranslation()) && bean.getScheme() == null) { errors.add(RESOURCE_BUNDLE.getString("error.config.translation_has_no_language_placeholders")); } + if (hasRelativePaths(bean.getTranslation())) { errors.add(RESOURCE_BUNDLE.getString("error.config.translation_contains_relative_paths")); } @@ -185,22 +192,31 @@ public List checkProperties(FileBean bean) { if (updateOption != null && !(updateOption.equals("update_as_unapproved") || updateOption.equals("update_without_changes"))) { errors.add(RESOURCE_BUNDLE.getString("error.config.update_option")); } + Integer escQuotes = bean.getEscapeQuotes(); if (escQuotes != null && (escQuotes < 0 || escQuotes > 3)) { errors.add(RESOURCE_BUNDLE.getString("error.config.escape_quotes")); } + Integer escSpecialCharacters = bean.getEscapeSpecialCharacters(); if (escSpecialCharacters != null && (escSpecialCharacters < 0 || escSpecialCharacters > 1)) { errors.add(RESOURCE_BUNDLE.getString("error.config.escape_special_characters")); } + String exportQuotes = bean.getExportQuotes(); + if (exportQuotes != null && !(exportQuotes.equals("single") || exportQuotes.equals("double"))) { + errors.add(RESOURCE_BUNDLE.getString("error.config.export_quotes")); + } + if (StringUtils.isNotEmpty(bean.getDest()) && !checkDest(bean.getDest(), bean.getSource())) { errors.add(RESOURCE_BUNDLE.getString("error.dest_and_pattern_in_source")); } + if (bean.getSkipTranslatedOnly() != null && bean.getSkipUntranslatedFiles() != null && bean.getSkipTranslatedOnly() && bean.getSkipUntranslatedFiles()) { errors.add(RESOURCE_BUNDLE.getString("error.skip_untranslated_both_strings_and_files")); } + return errors; } } diff --git a/src/main/java/com/crowdin/cli/properties/PropertiesBuilder.java b/src/main/java/com/crowdin/cli/properties/PropertiesBuilder.java index 6368d3ed9..fd94ccce6 100644 --- a/src/main/java/com/crowdin/cli/properties/PropertiesBuilder.java +++ b/src/main/java/com/crowdin/cli/properties/PropertiesBuilder.java @@ -76,6 +76,8 @@ public abstract class PropertiesBuilder public static final String ESCAPE_SPECIAL_CHARACTERS = "escape_special_characters"; + public static final String EXPORT_QUOTES = "export_quotes"; + public static final String MULTILINGUAL_SPREADSHEET = "multilingual_spreadsheet"; public static final String SCHEME = "scheme"; diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties index 4cddf8ada..9b8b3812a 100755 --- a/src/main/resources/messages/messages.properties +++ b/src/main/resources/messages/messages.properties @@ -474,6 +474,7 @@ error.config.translation_contains_relative_paths=The 'translation' parameter can error.config.update_option=Configuration file contains unexpected 'update_option' value. The expected values are 'update_as_unapproved' or 'update_without_changes' error.config.escape_quotes=Acceptable values for 'escape_quotes' are: 0, 1, 2, 3. Default is 3. Read more about 'escape_quotes': https://developer.crowdin.com/configuration-file/#escape-quotes-options-for-properties-file-format error.config.escape_special_characters=Acceptable values for 'escape_special_characters' are: 0 and 1. Default is 1. Read more about 'escape_special_characters': https://developer.crowdin.com/configuration-file/#escape-quotes-options-for-properties-file-format +error.config.export_quotes=Acceptable values for the 'export_quotes' are: 'single' and 'double'. Default is 'single' error.config.params_xor_source_translation=Both the 'source' and the 'translation' must be specified in the parameters error.config.params_dest='dest' must be specified with both 'source' and 'translation' parameters error.config.target_has_no_name=Required target 'name' field is missing in the configuration file diff --git a/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java index 8edc7ef1b..fc2519891 100644 --- a/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java +++ b/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java @@ -17,6 +17,9 @@ import com.crowdin.client.sourcefiles.model.AddFileRequest; import com.crowdin.client.sourcefiles.model.Branch; import com.crowdin.client.sourcefiles.model.Directory; +import com.crowdin.client.sourcefiles.model.ExportQuotes; +import com.crowdin.client.sourcefiles.model.GeneralFileExportOptions; +import com.crowdin.client.sourcefiles.model.JavaScriptFileExportOptions; import com.crowdin.client.sourcefiles.model.OtherFileImportOptions; import com.crowdin.client.sourcefiles.model.PropertyFileExportOptions; import com.crowdin.client.sourcefiles.model.SpreadsheetFileImportOptions; @@ -76,8 +79,7 @@ public void testUploadOneSource_EmptyProject() throws ResponseException { setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -114,8 +116,84 @@ public void testUploadOneSourceWithNullContentSegmentation_EmptyProject() throws setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); + setExportOptions(new GeneralFileExportOptions() {{ + setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); + }} + ); + }}; + verify(client).addSource(eq(addFileRequest)); + verifyNoMoreInteractions(client); + } + + @Test + public void testUploadPropertiesFileWithEscapeQuotes_EmptyProject() throws ResponseException { + project.addFile(Utils.normalizePath("first.properties"), "Hello, World!"); + NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder + .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%") + .setBasePath(project.getBasePath()); + PropertiesWithFiles pb = pbBuilder.build(); + pb.getFiles().get(0).setEscapeQuotes(1); + + ProjectClient client = mock(ProjectClient.class); + when(client.downloadFullProject()) + .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build()); + when(client.uploadStorage(eq("first.properties"), any())) + .thenReturn(1L); + + NewAction action = new UploadSourcesAction(null, false, false, true, false, false); + action.act(Outputter.getDefault(), pb, client); + + verify(client).downloadFullProject(); + verify(client).listLabels(); + verify(client).uploadStorage(eq("first.properties"), any()); + AddFileRequest addFileRequest = new AddFileRequest() {{ + setName("first.properties"); + setStorageId(1L); + setImportOptions(new OtherFileImportOptions() {{ + setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); + }} + ); setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setEscapeSpecialCharacters(1); + setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); + }} + ); + }}; + verify(client).addSource(eq(addFileRequest)); + verifyNoMoreInteractions(client); + } + + @Test + public void testUploadJavaScriptFileWithExportQuotes_EmptyProject() throws ResponseException { + project.addFile(Utils.normalizePath("first.js"), "Hello, World!"); + NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder + .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%") + .setBasePath(project.getBasePath()); + PropertiesWithFiles pb = pbBuilder.build(); + pb.getFiles().get(0).setExportQuotes("single"); + + ProjectClient client = mock(ProjectClient.class); + when(client.downloadFullProject()) + .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build()); + when(client.uploadStorage(eq("first.js"), any())) + .thenReturn(1L); + + NewAction action = new UploadSourcesAction(null, false, false, true, false, false); + action.act(Outputter.getDefault(), pb, client); + + verify(client).downloadFullProject(); + verify(client).listLabels(); + verify(client).uploadStorage(eq("first.js"), any()); + AddFileRequest addFileRequest = new AddFileRequest() {{ + setName("first.js"); + setStorageId(1L); + setImportOptions(new OtherFileImportOptions() {{ + setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); + }} + ); + setExportOptions(new JavaScriptFileExportOptions() {{ + setExportQuotes(ExportQuotes.SINGLE); setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -166,8 +244,7 @@ public void testUploadFewSourceWithDirectories_EmptyProject() throws ResponseExc setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -181,8 +258,7 @@ public void testUploadFewSourceWithDirectories_EmptyProject() throws ResponseExc setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -195,8 +271,7 @@ public void testUploadFewSourceWithDirectories_EmptyProject() throws ResponseExc setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -240,8 +315,7 @@ public void testUploadOneSourceWithBranch_EmptyProject() throws ResponseExceptio setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -278,8 +352,7 @@ public void testUploadOneSourceWithBranch_ProjectWithThatBranch() throws Respons setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -315,8 +388,7 @@ public void testUploadOneSourceWithDirectory_ProjectNotPreserveHierarchy() throw setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -354,8 +426,7 @@ public void testUploadOneSourceWithDirectory_ProjectWithPreserveHierarchy() thro setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -392,8 +463,7 @@ public void testUploadOneSourceWithDest_Project() throws ResponseException { setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -438,8 +508,7 @@ public void testUploadOneSourceWithDestAndDeleteObsoleteOption_Project() throws setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -478,8 +547,7 @@ public void testUpdateOneUploadOneSource_Project() throws ResponseException { setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -492,8 +560,7 @@ public void testUpdateOneUploadOneSource_Project() throws ResponseException { setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -538,8 +605,7 @@ public void testAddCsvFile_EmptyProject() throws ResponseException { setFirstLineContainsHeader(false); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -576,8 +642,7 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_1_Project() throws setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -622,8 +687,7 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_2_Project() throws setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); @@ -677,8 +741,7 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_3_Project() throws setContentSegmentation(pb.getFiles().get(0).getContentSegmentation()); }} ); - setExportOptions(new PropertyFileExportOptions() {{ - setEscapeQuotes(pb.getFiles().get(0).getEscapeQuotes()); + setExportOptions(new GeneralFileExportOptions() {{ setExportPattern(pb.getFiles().get(0).getTranslation().replaceAll("[\\\\/]+", "/")); }} ); diff --git a/website/docs/advanced.md b/website/docs/advanced.md index b22da810c..aebeb2c77 100644 --- a/website/docs/advanced.md +++ b/website/docs/advanced.md @@ -117,6 +117,57 @@ pseudo_localization: { Visit the [KB article](https://developer.crowdin.com/pseudolocalization/) to read more about Pseudo-Localiation. +### Export configuration for specific file formats + +#### Java Properties + +**Escape Quotes** + +The `escape_qutes` option defines whether a single quote should be escaped by another single quote or backslash in exported translations. You can add the `escape_quotes` per-file option. Acceptable values are `0`, `1`, `2`, `3`. Default is `3`. + +- `0` - do not escape +- `1` - escape single quote with another single quote +- `2` - escape single quote with a backslash +- `3` - escape single quote with another single quote only in strings containing variables ( {0} ) + +**Escape special characters** + +Defines whether any special characters (`=`, `:`, `!` and `#`) should be escaped by a backslash in exported translations. You can add the `escape_special_characters` per-file option. Acceptable values are `0`, `1`. Default is `1`. + +- `0` - do not escape special characters +- `1` - escape special characters by a backslash + +Example of the configuration: + +```yml title="crowdin.yml" +"files": [ + { + "source": "/en/strings.properties", + "translation": "/%two_letters_code%/%original_file_name%", + "escape_quotes": 1, + "escape_special_characters": 0 + } +] +``` + +#### JavaScript + +**Export Quotes** + +The `export_quotes` option defines the type of quotes to use in exported translations. You can add the `export_quotes` per-file option. Acceptable values are `single`, `double`. Default is `single`. + +Example of the configuration: + +```yml title="crowdin.yml" +"files": [ + { + "source": "/en/strings.js", + "translation": "/%two_letters_code%/%original_file_name%", + "export_quotes": "double" + } +] +``` + ### Configure export options for each file group There is a way to specify export options for each file-group in the `crowdin.yml` configuration file: