From a6db4502d88f215967eb11e97e3e990b22f484b3 Mon Sep 17 00:00:00 2001
From: Kateryna Oblakevych <malutina.catya14@gmail.com>
Date: Mon, 29 Jan 2024 09:50:12 +0200
Subject: [PATCH 1/5] feat: strings based project functionality

---
 .../cli/client/ClientDistribution.java        |  10 +-
 .../cli/client/CrowdinClientDistribution.java |  24 +-
 .../cli/client/CrowdinProjectClient.java      |  19 +-
 .../com/crowdin/cli/client/ProjectClient.java |   9 +-
 .../com/crowdin/cli/commands/Actions.java     |   4 +-
 .../cli/commands/actions/CliActions.java      |   8 +-
 .../actions/DistributionAddAction.java        |  44 +++-
 .../actions/DistributionReleaseAction.java    |  53 +++-
 .../commands/actions/PreTranslateAction.java  |  86 ++++---
 .../cli/commands/actions/StringAddAction.java |  26 +-
 .../commands/actions/StringListAction.java    |  23 +-
 .../commands/actions/UploadSourcesAction.java | 115 ++++++---
 .../actions/UploadTranslationsAction.java     | 229 +++++++++++-------
 .../functionality/RequestBuilder.java         |  39 ++-
 .../picocli/DistributionAddSubcommand.java    |   4 -
 .../DistributionReleaseSubcommand.java        |   6 +-
 .../commands/picocli/StringAddSubcommand.java |   5 +-
 .../cli/commands/actions/CliActionsTest.java  |   4 +-
 .../actions/DistributionAddActionTest.java    |  41 +++-
 .../DistributionReleaseActionTest.java        |  43 +++-
 .../commands/actions/StringAddActionTest.java |  52 +++-
 .../actions/StringListActionTest.java         |  33 ++-
 .../actions/UploadSourcesActionTest.java      | 119 +++++++--
 .../actions/UploadTranslationsActionTest.java |  63 ++++-
 .../DistributionAddSubcommandTest.java        |   7 +-
 .../commands/picocli/PicocliTestUtils.java    |   2 +-
 .../picocli/StringAddSubcommandTest.java      |   4 +-
 27 files changed, 801 insertions(+), 271 deletions(-)

diff --git a/src/main/java/com/crowdin/cli/client/ClientDistribution.java b/src/main/java/com/crowdin/cli/client/ClientDistribution.java
index 6e25a2aaf..59c66de8d 100644
--- a/src/main/java/com/crowdin/cli/client/ClientDistribution.java
+++ b/src/main/java/com/crowdin/cli/client/ClientDistribution.java
@@ -1,8 +1,6 @@
 package com.crowdin.cli.client;
 
-import com.crowdin.client.distributions.model.AddDistributionRequest;
-import com.crowdin.client.distributions.model.Distribution;
-import com.crowdin.client.distributions.model.DistributionRelease;
+import com.crowdin.client.distributions.model.*;
 
 import java.util.List;
 
@@ -12,7 +10,13 @@ public interface ClientDistribution extends Client {
 
     Distribution addDistribution(AddDistributionRequest request);
 
+    Distribution addDistributionStringsBased(AddDistributionStringsBasedRequest request);
+
     DistributionRelease release(String hash);
 
+    DistributionStringsBasedRelease releaseStringsBased(String hash);
+
     DistributionRelease getDistributionRelease(String hash);
+
+    DistributionStringsBasedRelease getDistributionStringsBasedRelease(String hash);
 }
diff --git a/src/main/java/com/crowdin/cli/client/CrowdinClientDistribution.java b/src/main/java/com/crowdin/cli/client/CrowdinClientDistribution.java
index b9be24b39..a3c3c5fb3 100644
--- a/src/main/java/com/crowdin/cli/client/CrowdinClientDistribution.java
+++ b/src/main/java/com/crowdin/cli/client/CrowdinClientDistribution.java
@@ -1,8 +1,6 @@
 package com.crowdin.cli.client;
 
-import com.crowdin.client.distributions.model.AddDistributionRequest;
-import com.crowdin.client.distributions.model.Distribution;
-import com.crowdin.client.distributions.model.DistributionRelease;
+import com.crowdin.client.distributions.model.*;
 
 import java.util.List;
 
@@ -29,6 +27,13 @@ public Distribution addDistribution(AddDistributionRequest distributionRequest)
             .getData());
     }
 
+    @Override
+    public Distribution addDistributionStringsBased(AddDistributionStringsBasedRequest distributionRequest) {
+        return executeRequest(() -> this.client.getDistributionsApi()
+            .addDistributionStringsBased(Long.valueOf(projectId), distributionRequest)
+            .getData());
+    }
+
     @Override
     public DistributionRelease release(String hash) {
         return executeRequest(() -> this.client.getDistributionsApi()
@@ -36,6 +41,13 @@ public DistributionRelease release(String hash) {
                                                .getData());
     }
 
+    @Override
+    public DistributionStringsBasedRelease releaseStringsBased(String hash) {
+        return executeRequest(() -> this.client.getDistributionsApi()
+                                               .createDistributionStringsBasedRelease(Long.valueOf(projectId), hash)
+                                               .getData());
+    }
+
     @Override
     public DistributionRelease getDistributionRelease(String hash) {
         return executeRequest(() -> this.client.getDistributionsApi()
@@ -43,4 +55,10 @@ public DistributionRelease getDistributionRelease(String hash) {
                                                .getData());
     }
 
+    @Override
+    public DistributionStringsBasedRelease getDistributionStringsBasedRelease(String hash) {
+        return executeRequest(() -> this.client.getDistributionsApi()
+                                               .getDistributionStringsBasedRelease(Long.valueOf(projectId), hash)
+                                               .getData());
+    }
 }
diff --git a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
index ccd96b7bf..babadd4c3 100644
--- a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
+++ b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
@@ -6,10 +6,7 @@
 import com.crowdin.client.projectsgroups.model.ProjectSettings;
 import com.crowdin.client.projectsgroups.model.Type;
 import com.crowdin.client.sourcefiles.model.*;
-import com.crowdin.client.sourcestrings.model.AddSourceStringRequest;
-import com.crowdin.client.sourcestrings.model.SourceString;
-import com.crowdin.client.sourcestrings.model.UploadStringsProgress;
-import com.crowdin.client.sourcestrings.model.UploadStringsRequest;
+import com.crowdin.client.sourcestrings.model.*;
 import com.crowdin.client.storage.model.Storage;
 import com.crowdin.client.stringcomments.model.AddStringCommentRequest;
 import com.crowdin.client.stringcomments.model.StringComment;
@@ -328,6 +325,13 @@ public SourceString addSourceString(AddSourceStringRequest request) {
             .getData());
     }
 
+    @Override
+    public SourceString addSourceStringStringsBased(AddSourceStringStringsBasedRequest request) {
+        return executeRequest(() -> this.client.getSourceStringsApi()
+            .addSourceStringStringsBased(this.projectId, request)
+            .getData());
+    }
+
     @Override
     public List<SourceString> listSourceString(Long fileId, Long branchId, String labelIds, String filter, String croql) {
         return executeRequestFullList((limit, offset) -> this.client.getSourceStringsApi()
@@ -391,6 +395,13 @@ public PreTranslationStatus startPreTranslation(ApplyPreTranslationRequest reque
             .getData());
     }
 
+    @Override
+    public PreTranslationStatus startPreTranslationStringsBased(ApplyPreTranslationStringsBasedRequest request) {
+        return executeRequest(() ->this.client.getTranslationsApi()
+            .applyPreTranslationStringsBased(this.projectId, request)
+            .getData());
+    }
+
     @Override
     public PreTranslationStatus checkPreTranslation(String preTranslationId) {
         return executeRequest(() -> this.client.getTranslationsApi()
diff --git a/src/main/java/com/crowdin/cli/client/ProjectClient.java b/src/main/java/com/crowdin/cli/client/ProjectClient.java
index 1b8d852d5..f1deb5f17 100644
--- a/src/main/java/com/crowdin/cli/client/ProjectClient.java
+++ b/src/main/java/com/crowdin/cli/client/ProjectClient.java
@@ -6,10 +6,7 @@
 import com.crowdin.client.labels.model.AddLabelRequest;
 import com.crowdin.client.labels.model.Label;
 import com.crowdin.client.sourcefiles.model.*;
-import com.crowdin.client.sourcestrings.model.AddSourceStringRequest;
-import com.crowdin.client.sourcestrings.model.SourceString;
-import com.crowdin.client.sourcestrings.model.UploadStringsProgress;
-import com.crowdin.client.sourcestrings.model.UploadStringsRequest;
+import com.crowdin.client.sourcestrings.model.*;
 import com.crowdin.client.stringcomments.model.AddStringCommentRequest;
 import com.crowdin.client.stringcomments.model.StringComment;
 import com.crowdin.client.translations.model.*;
@@ -83,6 +80,8 @@ default CrowdinProjectFull downloadFullProject() {
 
     SourceString addSourceString(AddSourceStringRequest request);
 
+    SourceString addSourceStringStringsBased(AddSourceStringStringsBasedRequest request);
+
     List<SourceString> listSourceString(Long fileId, Long branchId, String labelIds, String filter, String croql);
 
     void deleteSourceString(Long id);
@@ -101,5 +100,7 @@ default CrowdinProjectFull downloadFullProject() {
 
     PreTranslationStatus startPreTranslation(ApplyPreTranslationRequest request);
 
+    PreTranslationStatus startPreTranslationStringsBased(ApplyPreTranslationStringsBasedRequest request);
+
     PreTranslationStatus checkPreTranslation(String preTranslationId);
 }
diff --git a/src/main/java/com/crowdin/cli/commands/Actions.java b/src/main/java/com/crowdin/cli/commands/Actions.java
index 6a9702c96..997c17d40 100644
--- a/src/main/java/com/crowdin/cli/commands/Actions.java
+++ b/src/main/java/com/crowdin/cli/commands/Actions.java
@@ -48,7 +48,7 @@ NewAction<ProjectProperties, ProjectClient> status(
         boolean noProgress, String branchName, String languageId, String file, String directory, boolean isVerbose, boolean showTranslated, boolean showApproved, boolean failIfIncomplete);
 
     NewAction<ProjectProperties, ProjectClient> stringAdd(
-        boolean noProgress, String text, String identifier, Integer maxLength, String context, List<String> files, List<String> labelNames, Boolean hidden);
+        boolean noProgress, String text, String identifier, Integer maxLength, String context, List<String> files, List<String> labelNames, String branch, Boolean hidden);
 
     NewAction<ProjectProperties, ProjectClient> stringComment(boolean plainView,
         boolean noProgress, String text, String stringId, String language, String type, String issueType);
@@ -97,7 +97,7 @@ NewAction<ProjectProperties, ClientTask> taskAdd(
 
     NewAction<ProjectProperties, ClientDistribution> distributionAdd(boolean noProgress, boolean plainView, String name, ExportMode exportMode, List<String> files, List<Integer> bundleIds, String branch, ProjectClient projectClient);
 
-    NewAction<ProjectProperties, ClientDistribution> distributionRelease(boolean noProgress, boolean plainView, String hash);
+    NewAction<ProjectProperties, ClientDistribution> distributionRelease(boolean noProgress, boolean plainView, String hash, ProjectClient projectClient);
 
     NewAction<ProjectProperties, ClientComment> commentList(boolean plainView, boolean isVerbose, String stringId, com.crowdin.client.stringcomments.model.Type type, com.crowdin.client.issues.model.Type issueType, IssueStatus status);
 
diff --git a/src/main/java/com/crowdin/cli/commands/actions/CliActions.java b/src/main/java/com/crowdin/cli/commands/actions/CliActions.java
index 2d9a71fab..98518ffd3 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/CliActions.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/CliActions.java
@@ -80,9 +80,9 @@ public NewAction<ProjectProperties, ProjectClient> status(
 
     @Override
     public NewAction<ProjectProperties, ProjectClient> stringAdd(
-        boolean noProgress, String text, String identifier, Integer maxLength, String context, List<String> files, List<String> labelNames, Boolean hidden
+        boolean noProgress, String text, String identifier, Integer maxLength, String context, List<String> files, List<String> labelNames, String branch, Boolean hidden
     ) {
-        return new StringAddAction(noProgress, text, identifier, maxLength, context, files, labelNames, hidden);
+        return new StringAddAction(noProgress, text, identifier, maxLength, context, files, labelNames, branch, hidden);
     }
     @Override
     public NewAction<ProjectProperties, ProjectClient> stringComment(boolean plainView,
@@ -189,8 +189,8 @@ public NewAction<ProjectProperties, ClientDistribution> distributionAdd(boolean
     }
 
     @Override
-    public NewAction<ProjectProperties, ClientDistribution> distributionRelease(boolean noProgress, boolean plainView, String hash) {
-        return new DistributionReleaseAction(noProgress, plainView, hash);
+    public NewAction<ProjectProperties, ClientDistribution> distributionRelease(boolean noProgress, boolean plainView, String hash, ProjectClient projectClient) {
+        return new DistributionReleaseAction(noProgress, plainView, hash, projectClient);
     }
 
     @Override
diff --git a/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java b/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java
index a3f84615e..ded47b1a5 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java
@@ -10,8 +10,10 @@
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
 import com.crowdin.client.distributions.model.AddDistributionRequest;
+import com.crowdin.client.distributions.model.AddDistributionStringsBasedRequest;
 import com.crowdin.client.distributions.model.Distribution;
 import com.crowdin.client.distributions.model.ExportMode;
+import com.crowdin.client.projectsgroups.model.Type;
 import com.crowdin.client.sourcefiles.model.Branch;
 import com.crowdin.client.sourcefiles.model.FileInfo;
 import lombok.AllArgsConstructor;
@@ -19,11 +21,13 @@
 import java.nio.file.Paths;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
 import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
 import static com.crowdin.cli.utils.console.ExecutionStatus.OK;
+import static com.crowdin.client.distributions.model.ExportMode.DEFAULT;
 
 @AllArgsConstructor
 class DistributionAddAction implements NewAction<ProjectProperties, ClientDistribution> {
@@ -49,6 +53,9 @@ public void act(Outputter out, ProjectProperties pb, ClientDistribution client)
         );
         List<Long> fileIds = null;
         if (files != null) {
+            if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_file_string_project"));
+            }
             Map<String, Long> projectBranches = project.getBranches().values().stream()
                                                        .collect(Collectors.toMap(Branch::getName, Branch::getId));
             List<String> projectFiles = project.getFiles().stream()
@@ -74,19 +81,36 @@ public void act(Outputter out, ProjectProperties pb, ClientDistribution client)
                     .filter(file -> files.contains(file.getPath()))
                     .map(FileInfo::getId)
                     .collect(Collectors.toList());
+        } else if (exportMode == DEFAULT && Objects.equals(project.getType(), Type.FILES_BASED)) {
+            throw new RuntimeException(RESOURCE_BUNDLE.getString("error.distribution.empty_file"));
         }
 
-        Distribution distribution;
-        AddDistributionRequest addDistributionRequest = RequestBuilder.addDistribution(name, exportMode, fileIds, bundleIds);
-        Optional.ofNullable(name).ifPresent(addDistributionRequest::setName);
-        Optional.ofNullable(exportMode).ifPresent(addDistributionRequest::setExportMode);
-        Optional.ofNullable(fileIds).ifPresent(addDistributionRequest::setFileIds);
-        Optional.ofNullable(bundleIds).ifPresent(addDistributionRequest::setBundleIds);
+        Distribution distribution = null;
+        if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+            AddDistributionRequest addDistributionRequest = RequestBuilder.addDistribution(name, exportMode, fileIds, bundleIds);
+            Optional.ofNullable(name).ifPresent(addDistributionRequest::setName);
+            Optional.ofNullable(exportMode).ifPresent(addDistributionRequest::setExportMode);
+            Optional.ofNullable(fileIds).ifPresent(addDistributionRequest::setFileIds);
+            Optional.ofNullable(bundleIds).ifPresent(addDistributionRequest::setBundleIds);
 
-        try {
-            distribution = client.addDistribution(addDistributionRequest);
-        } catch (Exception e) {
-            throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.distribution_is_not_added"), addDistributionRequest), e);
+            try {
+                distribution = client.addDistribution(addDistributionRequest);
+            } catch (Exception e) {
+                throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.distribution_is_not_added"), addDistributionRequest), e);
+            }
+        } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            AddDistributionStringsBasedRequest addDistributionRequest = new AddDistributionStringsBasedRequest();
+            addDistributionRequest.setName(name);
+            if (Objects.isNull(bundleIds)) {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("error.distribution.empty_bundle_ids"));
+            }
+            addDistributionRequest.setBundleIds(bundleIds);
+
+            try {
+                distribution = client.addDistributionStringsBased(addDistributionRequest);
+            } catch (Exception e) {
+                throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.distribution_is_not_added"), addDistributionRequest), e);
+            }
         }
 
         if (!plainView) {
diff --git a/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java b/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
index 989837961..e03183ede 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
@@ -1,13 +1,19 @@
 package com.crowdin.cli.commands.actions;
 
 import com.crowdin.cli.client.ClientDistribution;
+import com.crowdin.cli.client.CrowdinProjectInfo;
+import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.commands.NewAction;
 import com.crowdin.cli.commands.Outputter;
 import com.crowdin.cli.properties.ProjectProperties;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
 import com.crowdin.client.distributions.model.DistributionRelease;
+import com.crowdin.client.distributions.model.DistributionStringsBasedRelease;
+import com.crowdin.client.projectsgroups.model.Type;
 import lombok.AllArgsConstructor;
 
+import java.util.Objects;
+
 import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
 import static com.crowdin.cli.utils.console.ExecutionStatus.OK;
 
@@ -18,13 +24,26 @@ class DistributionReleaseAction implements NewAction<ProjectProperties, ClientDi
     private boolean plainView;
     private String hash;
 
+    private ProjectClient projectClient;
+
     @Override
     public void act(Outputter out, ProjectProperties pb, ClientDistribution client) {
-        this.releaseDistribution(out, client);
+        CrowdinProjectInfo project = ConsoleSpinner.execute(
+            out,
+            "message.spinner.fetching_project_info", "error.collect_project_info",
+            this.noProgress,
+            this.plainView,
+            () -> this.projectClient.downloadProjectInfo()
+        );
+        if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+            this.releaseDistributionFilesBased(out, client);
+        } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            this.releaseDistributionStringsBased(out, client);
+        }
         out.println(OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.distribution.released"), hash)));
     }
 
-    private DistributionRelease releaseDistribution(Outputter out, ClientDistribution client) {
+    private DistributionRelease releaseDistributionFilesBased(Outputter out, ClientDistribution client) {
         return ConsoleSpinner.execute(
                 out,
                 "message.spinner.releasing_distribution",
@@ -53,4 +72,34 @@ private DistributionRelease releaseDistribution(Outputter out, ClientDistributio
                 }
         );
     }
+
+    private DistributionStringsBasedRelease releaseDistributionStringsBased(Outputter out, ClientDistribution client) {
+        return ConsoleSpinner.execute(
+                out,
+                "message.spinner.releasing_distribution",
+                "error.distribution_is_not_released",
+                this.noProgress,
+                false,
+                () -> {
+                    DistributionStringsBasedRelease release = client.releaseStringsBased(hash);
+
+                    while (!"success".equalsIgnoreCase(release.getStatus())) {
+                        ConsoleSpinner.update(
+                                String.format(RESOURCE_BUNDLE.getString("message.spinner.releasing_distribution_percents"),
+                                              release.getProgress()));
+                        Thread.sleep(1000);
+
+                        release = client.getDistributionStringsBasedRelease(hash);
+
+                        if ("failed".equalsIgnoreCase(release.getStatus())) {
+                            throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
+                        }
+                    }
+
+                    ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.releasing_distribution_percents"), 100));
+
+                    return release;
+                }
+        );
+    }
 }
diff --git a/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java b/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
index 8af7f492f..39b723f8c 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
@@ -6,28 +6,21 @@
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.commands.NewAction;
 import com.crowdin.cli.commands.Outputter;
-import com.crowdin.cli.commands.functionality.ProjectFilesUtils;
-import com.crowdin.cli.commands.functionality.PropertiesBeanUtils;
-import com.crowdin.cli.commands.functionality.RequestBuilder;
-import com.crowdin.cli.commands.functionality.SourcesUtils;
+import com.crowdin.cli.commands.functionality.*;
 import com.crowdin.cli.properties.PropertiesWithFiles;
 import com.crowdin.cli.utils.PlaceholderUtil;
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
 import com.crowdin.client.labels.model.Label;
 import com.crowdin.client.languages.model.Language;
+import com.crowdin.client.projectsgroups.model.Type;
+import com.crowdin.client.sourcefiles.model.Branch;
 import com.crowdin.client.sourcefiles.model.FileInfo;
-import com.crowdin.client.translations.model.ApplyPreTranslationRequest;
-import com.crowdin.client.translations.model.AutoApproveOption;
-import com.crowdin.client.translations.model.Method;
-import com.crowdin.client.translations.model.PreTranslationStatus;
+import com.crowdin.client.translations.model.*;
 import org.apache.commons.lang3.StringUtils;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
@@ -74,18 +67,27 @@ public void act(Outputter out, PropertiesWithFiles properties, ProjectClient cli
             this.noProgress, this.plainView, () -> client.downloadFullProject(this.branchName));
 
         List<String> languages = this.prepareLanguageIds(project);
-        List<Long> fileIds = this.prepareFileIds(out, properties, project);
         List<Long> labelIds = this.prepareLabelIds(out, client);
 
-        if (fileIds == null || fileIds.isEmpty()) {
-            throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.no_files_found_for_pre_translate")));
+        if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+            List<Long> fileIds = this.prepareFileIds(out, properties, project);
+            if (fileIds == null || fileIds.isEmpty()) {
+                throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.no_files_found_for_pre_translate")));
+            }
+            ApplyPreTranslationRequest request = RequestBuilder.applyPreTranslation(
+                languages, fileIds, method, engineId, autoApproveOption,
+                duplicateTranslations, translateUntranslatedOnly, translateWithPerfectMatchOnly, labelIds);
+            this.applyPreTranslation(out, client, request);
+        } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            Branch branch = BranchUtils.getOrCreateBranch(out, branchName, client, project, false);
+            if (Objects.isNull(branch)) {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"));
+            }
+            ApplyPreTranslationStringsBasedRequest request = RequestBuilder.applyPreTranslationStringsBased(
+                languages, Collections.singletonList(branch.getId()), method, engineId, autoApproveOption,
+                duplicateTranslations, translateUntranslatedOnly, translateWithPerfectMatchOnly, labelIds);
+            this.applyPreTranslationStringsBased(out, client, request);
         }
-
-        ApplyPreTranslationRequest request = RequestBuilder.applyPreTranslation(
-            languages, fileIds, method, engineId, autoApproveOption,
-            duplicateTranslations, translateUntranslatedOnly, translateWithPerfectMatchOnly, labelIds);
-
-        this.applyPreTranslation(out, client, request);
     }
 
     private List<String> prepareLanguageIds(CrowdinProjectInfo projectInfo) {
@@ -176,19 +178,25 @@ private void applyPreTranslation(Outputter out, ProjectClient client, ApplyPreTr
             this.plainView,
             () -> {
                 PreTranslationStatus preTranslationStatus = client.startPreTranslation(request);
+                preTranslationStatus = handlePreTranslationStatus(client, preTranslationStatus);
 
-                while (!preTranslationStatus.getStatus().equalsIgnoreCase("finished")) {
-                    ConsoleSpinner.update(
-                            String.format(RESOURCE_BUNDLE.getString("message.spinner.pre_translate_percents"),
-                                    Math.toIntExact(preTranslationStatus.getProgress())));
-                    Thread.sleep(1000);
+                ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.pre_translate_done"), 100));
 
-                    preTranslationStatus = client.checkPreTranslation(preTranslationStatus.getIdentifier());
+                return preTranslationStatus;
+            }
+        );
+    }
 
-                    if (preTranslationStatus.getStatus().equalsIgnoreCase("failed")) {
-                        throw new RuntimeException();
-                    }
-                }
+    private void applyPreTranslationStringsBased(Outputter out, ProjectClient client, ApplyPreTranslationStringsBasedRequest request) {
+        ConsoleSpinner.execute(
+            out,
+            "message.spinner.pre_translate",
+            "error.spinner.pre_translate",
+            this.noProgress,
+            this.plainView,
+            () -> {
+                PreTranslationStatus preTranslationStatus = client.startPreTranslationStringsBased(request);
+                preTranslationStatus = handlePreTranslationStatus(client, preTranslationStatus);
 
                 ConsoleSpinner.update(String.format(RESOURCE_BUNDLE.getString("message.spinner.pre_translate_done"), 100));
 
@@ -196,4 +204,20 @@ private void applyPreTranslation(Outputter out, ProjectClient client, ApplyPreTr
             }
         );
     }
+
+    private PreTranslationStatus handlePreTranslationStatus(ProjectClient client, PreTranslationStatus preTranslationStatus) throws InterruptedException {
+        while (!preTranslationStatus.getStatus().equalsIgnoreCase("finished")) {
+            ConsoleSpinner.update(
+                    String.format(RESOURCE_BUNDLE.getString("message.spinner.pre_translate_percents"),
+                            Math.toIntExact(preTranslationStatus.getProgress())));
+            Thread.sleep(1000);
+
+            preTranslationStatus = client.checkPreTranslation(preTranslationStatus.getIdentifier());
+
+            if (preTranslationStatus.getStatus().equalsIgnoreCase("failed")) {
+                throw new RuntimeException();
+            }
+        }
+        return preTranslationStatus;
+    }
 }
diff --git a/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java b/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java
index bf203459d..1cc425b81 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java
@@ -4,16 +4,21 @@
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.commands.NewAction;
 import com.crowdin.cli.commands.Outputter;
+import com.crowdin.cli.commands.functionality.BranchUtils;
 import com.crowdin.cli.commands.functionality.ProjectFilesUtils;
 import com.crowdin.cli.commands.functionality.RequestBuilder;
 import com.crowdin.cli.properties.ProjectProperties;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
 import com.crowdin.client.labels.model.Label;
+import com.crowdin.client.projectsgroups.model.Type;
+import com.crowdin.client.sourcefiles.model.Branch;
 import com.crowdin.client.sourcefiles.model.FileInfo;
 import com.crowdin.client.sourcestrings.model.AddSourceStringRequest;
+import com.crowdin.client.sourcestrings.model.AddSourceStringStringsBasedRequest;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
@@ -29,10 +34,11 @@ class StringAddAction implements NewAction<ProjectProperties, ProjectClient> {
     private final String context;
     private final List<String> files;
     private final List<String> labelNames;
+    private final String branchName;
     private final Boolean hidden;
 
     public StringAddAction(
-        boolean noProgress, String text, String identifier, Integer maxLength, String context, List<String> files, List<String> labelNames, Boolean hidden
+        boolean noProgress, String text, String identifier, Integer maxLength, String context, List<String> files, List<String> labelNames, String branchName, Boolean hidden
     ) {
         this.noProgress = noProgress;
         this.text = text;
@@ -41,6 +47,7 @@ public StringAddAction(
         this.context = context;
         this.files = files;
         this.labelNames = labelNames;
+        this.branchName = branchName;
         this.hidden = hidden;
     }
 
@@ -52,10 +59,22 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         List<Long> labelIds = (labelNames != null && !labelNames.isEmpty()) ? this.prepareLabelIds(client) : null;
 
         if (files == null || files.isEmpty()) {
-            AddSourceStringRequest request = RequestBuilder.addString(this.text, this.identifier, this.maxLength, this.context, null, this.hidden, labelIds);
-            client.addSourceString(request);
+            if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+                Branch branch = BranchUtils.getOrCreateBranch(out, branchName, client, project, false);
+                if (Objects.isNull(branch)) {
+                    throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"));
+                }
+                AddSourceStringStringsBasedRequest request = RequestBuilder.addStringStringsBased(this.text, this.identifier, this.maxLength, this.context, branch.getId(), this.hidden, labelIds);
+                client.addSourceStringStringsBased(request);
+            } else {
+                AddSourceStringRequest request = RequestBuilder.addString(this.text, this.identifier, this.maxLength, this.context, null, this.hidden, labelIds);
+                client.addSourceString(request);
+            }
             out.println(OK.withIcon(RESOURCE_BUNDLE.getString("message.source_string_uploaded")));
         } else {
+            if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_file_string_project"));
+            }
             Map<String, FileInfo> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
             boolean containsError = false;
             for (String file : files) {
@@ -79,7 +98,6 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
                 throw new RuntimeException();
             }
         }
-
     }
 
     private List<Long> prepareLabelIds(ProjectClient client) {
diff --git a/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java b/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java
index 1497756d7..3601da382 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java
@@ -9,6 +9,7 @@
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
 import com.crowdin.client.labels.model.Label;
+import com.crowdin.client.projectsgroups.model.Type;
 import com.crowdin.client.sourcefiles.model.Branch;
 import com.crowdin.client.sourcefiles.model.FileInfo;
 import com.crowdin.client.sourcestrings.model.SourceString;
@@ -16,6 +17,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
@@ -52,10 +54,16 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         Map<Long, String> labels = client.listLabels().stream()
             .collect(Collectors.toMap(Label::getId, Label::getTitle));
 
-        Map<String, FileInfo> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
-        Map<Long, String> reversePaths = paths.entrySet()
-            .stream()
-            .collect(Collectors.toMap((entry) -> entry.getValue().getId(), Map.Entry::getKey));
+        boolean isFileBasedProject = Objects.equals(project.getType(), Type.FILES_BASED);
+        Map<String, FileInfo> paths = null;
+        Map<Long, String> reversePaths = null;
+        if (isFileBasedProject) {
+            paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
+            reversePaths = paths.entrySet()
+                .stream()
+                .collect(Collectors.toMap((entry) -> entry.getValue().getId(), Map.Entry::getKey));
+        }
+        Map<Long, String> finalReversePaths = reversePaths;
 
         String encodedFilter = filter != null ? Utils.encodeURL(filter) : null;
         String encodedCroql = croql != null ? Utils.encodeURL(croql) : null;
@@ -64,6 +72,9 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         if (StringUtils.isEmpty(file)) {
             sourceStrings = client.listSourceString(null, branchId, null, encodedFilter, encodedCroql);
         } else {
+            if (!isFileBasedProject) {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_file_string_project"));
+            }
             if (paths.containsKey(file)) {
                 sourceStrings = client.listSourceString(paths.get(file).getId(), branchId, null, encodedFilter, encodedCroql);
             } else {
@@ -86,8 +97,8 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
                     out.println(String.format(
                         RESOURCE_BUNDLE.getString("message.source_string_list_context"), ss.getContext().trim().replaceAll("\n", "\n\t\t")));
                 }
-                if (ss.getFileId() != null) {
-                    out.println(String.format(RESOURCE_BUNDLE.getString("message.source_string_list_file"), reversePaths.get(ss.getFileId())));
+                if (isFileBasedProject && (ss.getFileId() != null)) {
+                    out.println(String.format(RESOURCE_BUNDLE.getString("message.source_string_list_file"), finalReversePaths.get(ss.getFileId())));
                 }
                 if (ss.getMaxLength() != null && ss.getMaxLength() != 0) {
                     out.println(String.format(RESOURCE_BUNDLE.getString("message.source_string_list_max_length"), ss.getMaxLength()));
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 cd31356c6..edbd0a4a5 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java
@@ -1,10 +1,6 @@
 package com.crowdin.cli.commands.actions;
 
-import com.crowdin.cli.client.CrowdinProjectFull;
-import com.crowdin.cli.client.EmptyFileException;
-import com.crowdin.cli.client.ExistsResponseException;
-import com.crowdin.cli.client.FileInUpdateException;
-import com.crowdin.cli.client.ProjectClient;
+import com.crowdin.cli.client.*;
 import com.crowdin.cli.commands.NewAction;
 import com.crowdin.cli.commands.Outputter;
 import com.crowdin.cli.commands.actions.subactions.DeleteObsoleteProjectFilesSubAction;
@@ -15,23 +11,12 @@
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.cli.utils.concurrency.ConcurrencyUtil;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
-import com.crowdin.cli.utils.console.ExecutionStatus;
 import com.crowdin.client.core.model.PatchRequest;
 import com.crowdin.client.labels.model.Label;
 import com.crowdin.client.languages.model.Language;
-import com.crowdin.client.sourcefiles.model.AddBranchRequest;
-import com.crowdin.client.sourcefiles.model.AddFileRequest;
-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 com.crowdin.client.projectsgroups.model.Type;
+import com.crowdin.client.sourcefiles.model.*;
+import com.crowdin.client.sourcestrings.model.UploadStringsRequest;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
 
@@ -42,16 +27,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
-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.cli.utils.console.ExecutionStatus.*;
 import static com.crowdin.client.sourcefiles.model.ExportQuotes.DOUBLE;
+import static com.crowdin.client.sourcefiles.model.ExportQuotes.SINGLE;
 
 class UploadSourcesAction implements NewAction<PropertiesWithFiles, ProjectClient> {
 
@@ -92,18 +74,26 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
         Branch branch = (branchName != null) ? BranchUtils.getOrCreateBranch(out, branchName, client, project, plainView) : null;
         Long branchId = (branch != null) ? branch.getId() : null;
 
-        Map<String, Long> directoryPaths = ProjectFilesUtils.buildDirectoryPaths(project.getDirectories(), project.getBranches())
-                .entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
-        Map<String, FileInfo> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
-
+        boolean isFilesBasedProject = Objects.equals(Type.FILES_BASED, project.getType());
+        Map<String, Long> directoryPaths = null;
+        Map<String, FileInfo> paths = null;
         DeleteObsoleteProjectFilesSubAction deleteObsoleteProjectFilesSubAction = new DeleteObsoleteProjectFilesSubAction(out, client);
-        if (deleteObsolete) {
-            Map<String, Long> directories = ProjectFilesUtils.buildDirectoryPaths(project.getDirectories(branchId))
+
+        if (isFilesBasedProject) {
+            directoryPaths = ProjectFilesUtils.buildDirectoryPaths(project.getDirectories(), project.getBranches())
                 .entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
-            Map<String, com.crowdin.client.sourcefiles.model.File> projectFiles = ProjectFilesUtils.buildFilePaths(project.getDirectories(branchId), project.getFiles(branchId));
-            deleteObsoleteProjectFilesSubAction.setData(projectFiles, directories, pb.getPreserveHierarchy(), this.plainView);
+            paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
+            if (deleteObsolete) {
+                Map<String, Long> directories = ProjectFilesUtils.buildDirectoryPaths(project.getDirectories(branchId))
+                    .entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
+                Map<String, com.crowdin.client.sourcefiles.model.File> projectFiles = ProjectFilesUtils.buildFilePaths(project.getDirectories(branchId), project.getFiles(branchId));
+                deleteObsoleteProjectFilesSubAction.setData(projectFiles, directories, pb.getPreserveHierarchy(), this.plainView);
+            }
         }
 
+        Map<String, Long> finalDirectoryPaths = directoryPaths;
+        Map<String, FileInfo> finalPaths = paths;
+
         List<String> uploadedSources = new ArrayList<>();
 
         Map<String, Long> labels = client.listLabels().stream()
@@ -185,8 +175,8 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                             uploadedSources.add(fileFullPath);
                         }
 
-                        FileInfo projectFile = paths.get(fileFullPath);
-                        if (autoUpdate && projectFile != null) {
+                        FileInfo projectFile = isFilesBasedProject ? finalPaths.get(fileFullPath) : null;
+                        if (isFilesBasedProject && autoUpdate && projectFile != null) {
                             final UpdateFileRequest request = new UpdateFileRequest();
                             request.setExportOptions(buildExportOptions(sourceFile, file, pb.getBasePath()));
                             request.setImportOptions(buildImportOptions(sourceFile, file, srxStorageId));
@@ -235,7 +225,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                                     throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.uploading_file"), fileFullPath), e);
                                 }
                             };
-                        } else if (projectFile == null) {
+                        } else if (projectFile == null && isFilesBasedProject) {
                             final AddFileRequest request = new AddFileRequest();
                             request.setName(fileName);
                             request.setExportOptions(buildExportOptions(sourceFile, file, pb.getBasePath()));
@@ -254,7 +244,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                             return (Runnable) () -> {
                                 Long directoryId;
                                 try {
-                                    directoryId = ProjectUtils.createPath(out, client, directoryPaths, filePath, branch, plainView);
+                                    directoryId = ProjectUtils.createPath(out, client, finalDirectoryPaths, filePath, branch, plainView);
                                 } catch (Exception e) {
                                     errorsPresented.set(true);
                                     throw new RuntimeException(RESOURCE_BUNDLE.getString("error.creating_directories"), e);
@@ -268,7 +258,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
 
                                 try (InputStream fileStream = new FileInputStream(sourceFile)) {
                                     request.setStorageId(client.uploadStorage(source.substring(source.lastIndexOf(Utils.PATH_SEPARATOR) + 1), fileStream));
-                                } catch (EmptyFileException e){
+                                } catch (EmptyFileException e) {
                                     errorsPresented.set(false);
                                     out.println(SKIPPED.withIcon(String.format(RESOURCE_BUNDLE.getString("message.uploading_file_skipped"), fileFullPath)));
                                     return;
@@ -292,6 +282,47 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                                     out.println(fileFullPath);
                                 }
                             };
+                        } else if (Objects.equals(Type.STRINGS_BASED, project.getType())) {
+                            final UploadStringsRequest request = new UploadStringsRequest();
+                            request.setImportOptions(buildImportOptionsStringsBased(sourceFile, file, srxStorageId));
+                            if (file.getType() != null) {
+                                request.setType(file.getType());
+                            }
+                            if (file.getLabels() != null) {
+                                List<Long> labelsIds = file.getLabels().stream().map(labels::get)
+                                    .collect(Collectors.toList());
+                                request.setLabelIds(labelsIds);
+                            }
+
+                            return (Runnable) () -> {
+                                if (branch == null) {
+                                    throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"));
+                                }
+                                request.setBranchId(branch.getId());
+
+                                try (InputStream fileStream = new FileInputStream(sourceFile)) {
+                                    request.setStorageId(client.uploadStorage(source.substring(source.lastIndexOf(Utils.PATH_SEPARATOR) + 1), fileStream));
+                                } catch (EmptyFileException e) {
+                                    errorsPresented.set(false);
+                                    out.println(SKIPPED.withIcon(String.format(RESOURCE_BUNDLE.getString("message.uploading_file_skipped"), fileFullPath)));
+                                    return;
+                                } catch (Exception e) {
+                                    errorsPresented.set(true);
+                                    throw new RuntimeException(
+                                        String.format(RESOURCE_BUNDLE.getString("error.upload_to_storage"), sourceFile.getAbsolutePath()), e);
+                                }
+                                try {
+                                    client.addSourceStringsBased(request);
+                                } catch (Exception e) {
+                                    errorsPresented.set(true);
+                                    throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.uploading_file"), fileFullPath), e);
+                                }
+                                if (!plainView) {
+                                    out.println(OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.uploading_file"), fileFullPath)));
+                                } else {
+                                    out.println(fileFullPath);
+                                }
+                            };
                         } else {
                             return (Runnable) () -> {
                                 if (!plainView) {
@@ -338,6 +369,16 @@ private ImportOptions buildImportOptions(java.io.File sourceFile, FileBean fileB
         }
     }
 
+    private com.crowdin.client.sourcestrings.model.ImportOptions buildImportOptionsStringsBased(java.io.File sourceFile, FileBean fileBean, Long srxStorageId) {
+        com.crowdin.client.sourcestrings.model.ImportOptions importOptions = new com.crowdin.client.sourcestrings.model.ImportOptions();
+        if (isSpreadsheet(sourceFile, fileBean)) {
+            importOptions.setFirstLineContainsHeader(fileBean.getFirstLineContainsHeader());
+            importOptions.setScheme(PropertiesBeanUtils.getSchemeObject(fileBean.getScheme()));
+            importOptions.setImportTranslations(fileBean.getImportTranslations());
+        }
+        return importOptions;
+    }
+
     private boolean isSpreadsheet(java.io.File file, FileBean fileBean) {
         return (fileBean.getDest() != null)
             ? FilenameUtils.isExtension(fileBean.getDest(), "csv", "xls", "xlsx")
diff --git a/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java b/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
index 2a40031ab..8f6b8b392 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
@@ -18,17 +18,17 @@
 import com.crowdin.cli.utils.concurrency.ConcurrencyUtil;
 import com.crowdin.cli.utils.console.ConsoleSpinner;
 import com.crowdin.client.languages.model.Language;
+import com.crowdin.client.projectsgroups.model.Type;
+import com.crowdin.client.sourcefiles.model.Branch;
 import com.crowdin.client.sourcefiles.model.File;
 import com.crowdin.client.translations.model.UploadTranslationsRequest;
+import com.crowdin.client.translations.model.UploadTranslationsStringsRequest;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 
 import java.io.FileInputStream;
 import java.io.InputStream;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
@@ -81,8 +81,6 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
 
         LanguageMapping serverLanguageMapping = project.getLanguageMapping();
 
-        Map<String, File> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFiles());
-
         List<Language> languages = (languageId != null)
             ? project.findLanguageById(languageId, true)
                 .map(Collections::singletonList)
@@ -105,108 +103,159 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                 }
                 continue;
             }
-
-            Map<java.io.File, Pair<List<Language>, UploadTranslationsRequest>> preparedRequests = new HashMap<>();
-            String branchPath = (StringUtils.isNotEmpty(this.branchName) ? branchName + Utils.PATH_SEPARATOR : "");
+            List<Runnable> tasks = null;
             AtomicBoolean containsErrors = new AtomicBoolean(false);
-            fileSourcesWithoutIgnores.forEach(source -> {
-                String filePath = branchPath + (StringUtils.isNotEmpty(file.getDest())
-                    ? PropertiesBeanUtils.prepareDest(file.getDest(), StringUtils.removeStart(source, pb.getBasePath()), placeholderUtil)
-                    : StringUtils.removeStart(source, pb.getBasePath() + commonPath));
-
-                if (!paths.containsKey(filePath)) {
-                    containsErrors.set(true);
-                    if (!plainView) {
-                        out.println(ERROR.withIcon(String.format(
-                            RESOURCE_BUNDLE.getString("error.source_not_exists_in_project"),
-                            StringUtils.removeStart(source, pb.getBasePath()), filePath)));
-                    }
-                    return;
-                }
-                Long fileId = paths.get(filePath).getId();
+            if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+                Map<String, File> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFiles());
+                Map<java.io.File, Pair<List<Language>, UploadTranslationsRequest>> preparedRequests = new HashMap<>();
+                String branchPath = (StringUtils.isNotEmpty(this.branchName) ? branchName + Utils.PATH_SEPARATOR : "");
+                fileSourcesWithoutIgnores.forEach(source -> {
+                    String filePath = branchPath + (StringUtils.isNotEmpty(file.getDest())
+                        ? PropertiesBeanUtils.prepareDest(file.getDest(), StringUtils.removeStart(source, pb.getBasePath()), placeholderUtil)
+                        : StringUtils.removeStart(source, pb.getBasePath() + commonPath));
 
-//                build filePath to each source and project language
-                String fileSource = StringUtils.removeStart(source, pb.getBasePath());
-                String translation = TranslationsUtils.replaceDoubleAsterisk(file.getSource(), file.getTranslation(), fileSource);
-                translation = placeholderUtil.replaceFileDependentPlaceholders(translation, new java.io.File(source));
-                if (file.getScheme() != null && !PlaceholderUtil.containsLangPlaceholders(translation)) {
-                    java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + translation);
-                    if (!transFile.exists()) {
+                    if (!paths.containsKey(filePath)) {
+                        containsErrors.set(true);
                         if (!plainView) {
-                            out.println(SKIPPED.withIcon(String.format(
-                                RESOURCE_BUNDLE.getString("error.translation_not_exists"),
-                                StringUtils.removeStart(transFile.getAbsolutePath(), pb.getBasePath()))));
+                            out.println(ERROR.withIcon(String.format(
+                                RESOURCE_BUNDLE.getString("error.source_not_exists_in_project"),
+                                StringUtils.removeStart(source, pb.getBasePath()), filePath)));
                         }
                         return;
                     }
-                    UploadTranslationsRequest request = RequestBuilder.uploadTranslations(fileId, importEqSuggestions, autoApproveImported, translateHidden);
-                    preparedRequests.put(transFile, Pair.of(languages, request));
-                } else {
-                    for (Language language : languages) {
-                        LanguageMapping localLanguageMapping =
-                            LanguageMapping.fromConfigFileLanguageMapping(file.getLanguagesMapping());
-                        LanguageMapping languageMapping =
-                            LanguageMapping.populate(localLanguageMapping, serverLanguageMapping);
-
-                        String transFileName = placeholderUtil.replaceLanguageDependentPlaceholders(translation, languageMapping, language);
-                        transFileName = PropertiesBeanUtils.useTranslationReplace(transFileName, file.getTranslationReplace());
-                        java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + transFileName);
+                    Long fileId = paths.get(filePath).getId();
+
+//                build filePath to each source and project language
+                    String fileSource = StringUtils.removeStart(source, pb.getBasePath());
+                    String translation = TranslationsUtils.replaceDoubleAsterisk(file.getSource(), file.getTranslation(), fileSource);
+                    translation = placeholderUtil.replaceFileDependentPlaceholders(translation, new java.io.File(source));
+                    if (file.getScheme() != null && !PlaceholderUtil.containsLangPlaceholders(translation)) {
+                        java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + translation);
                         if (!transFile.exists()) {
                             if (!plainView) {
                                 out.println(SKIPPED.withIcon(String.format(
                                     RESOURCE_BUNDLE.getString("error.translation_not_exists"),
                                     StringUtils.removeStart(transFile.getAbsolutePath(), pb.getBasePath()))));
                             }
-                            continue;
+                            return;
                         }
                         UploadTranslationsRequest request = RequestBuilder.uploadTranslations(fileId, importEqSuggestions, autoApproveImported, translateHidden);
-                        preparedRequests.put(transFile, Pair.of(Collections.singletonList(language), request));
+                        preparedRequests.put(transFile, Pair.of(languages, request));
+                    } else {
+                        for (Language language : languages) {
+                            LanguageMapping localLanguageMapping =
+                                LanguageMapping.fromConfigFileLanguageMapping(file.getLanguagesMapping());
+                            LanguageMapping languageMapping =
+                                LanguageMapping.populate(localLanguageMapping, serverLanguageMapping);
+
+                            String transFileName = placeholderUtil.replaceLanguageDependentPlaceholders(translation, languageMapping, language);
+                            transFileName = PropertiesBeanUtils.useTranslationReplace(transFileName, file.getTranslationReplace());
+                            java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + transFileName);
+                            if (!transFile.exists()) {
+                                if (!plainView) {
+                                    out.println(SKIPPED.withIcon(String.format(
+                                        RESOURCE_BUNDLE.getString("error.translation_not_exists"),
+                                        StringUtils.removeStart(transFile.getAbsolutePath(), pb.getBasePath()))));
+                                }
+                                continue;
+                            }
+                            UploadTranslationsRequest request = RequestBuilder.uploadTranslations(fileId, importEqSuggestions, autoApproveImported, translateHidden);
+                            preparedRequests.put(transFile, Pair.of(Collections.singletonList(language), request));
+                        }
                     }
+                });
+
+                tasks = preparedRequests.entrySet()
+                    .stream()
+                    .map(entry -> (Runnable) () -> {
+                        java.io.File translationFile = entry.getKey();
+                        List<Language> langs = entry.getValue().getLeft();
+                        UploadTranslationsRequest request = entry.getValue().getRight();
+                        try (InputStream fileStream = new FileInputStream(translationFile)) {
+                            Long storageId = client.uploadStorage(translationFile.getName(), fileStream);
+                            request.setStorageId(storageId);
+                        } catch (Exception e) {
+                            containsErrors.set(true);
+                            throw new RuntimeException(String.format(
+                                RESOURCE_BUNDLE.getString("error.upload_translation_to_storage"),
+                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
+                            ), e);
+                        }
+                        try {
+                            for (Language lang : langs) {
+                                try {
+                                    client.uploadTranslations(lang.getId(), request);
+                                } catch (WrongLanguageException e) {
+                                    out.println(WARNING.withIcon(String.format(
+                                        RESOURCE_BUNDLE.getString("message.warning.file_not_uploaded_cause_of_language"),
+                                        StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()), lang.getName())));
+                                }
+                            }
+                        } catch (Exception e) {
+                            containsErrors.set(true);
+                            throw new RuntimeException(String.format(
+                                RESOURCE_BUNDLE.getString("error.upload_translation"),
+                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
+                            ), e);
+                        }
+                        if (!plainView) {
+                            out.println(OK.withIcon(String.format(
+                                RESOURCE_BUNDLE.getString("message.translation_uploaded"),
+                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()))));
+                        } else {
+                            out.println(StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()));
+                        }
+                    })
+                    .collect(Collectors.toList());
+            } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+                Map<java.io.File, Pair<List<Language>, UploadTranslationsStringsRequest>> preparedRequests = new HashMap<>();
+                Branch branch = project.findBranchByName(branchName)
+                    .orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project")));
+                for (Language language : languages) {
+                    UploadTranslationsStringsRequest request = new UploadTranslationsStringsRequest();
+                    request.setBranchId(branch.getId());
+                    request.setTranslateHidden(translateHidden);
+                    request.setAutoApproveImported(autoApproveImported);
+                    request.setImportEqSuggestions(importEqSuggestions);
+                    preparedRequests.put(new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + file.getTranslation()), Pair.of(Collections.singletonList(language), request));
                 }
-            });
-
-            List<Runnable> tasks = preparedRequests.entrySet()
-                .stream()
-                .map(entry -> (Runnable) () -> {
-                    java.io.File translationFile = entry.getKey();
-                    List<Language> langs = entry.getValue().getLeft();
-                    UploadTranslationsRequest request = entry.getValue().getRight();
-                    try (InputStream fileStream = new FileInputStream(translationFile)) {
-                        Long storageId = client.uploadStorage(translationFile.getName(), fileStream);
-                        request.setStorageId(storageId);
-                    } catch (Exception e) {
-                        containsErrors.set(true);
-                        throw new RuntimeException(String.format(
-                            RESOURCE_BUNDLE.getString("error.upload_translation_to_storage"),
-                            StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
-                        ), e);
-                    }
-                    try {
-                        for (Language lang : langs) {
-                            try {
-                                client.uploadTranslations(lang.getId(), request);
-                            } catch (WrongLanguageException e) {
-                                out.println(WARNING.withIcon(String.format(
-                                    RESOURCE_BUNDLE.getString("message.warning.file_not_uploaded_cause_of_language"),
-                                    StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()), lang.getName())));
+                tasks = preparedRequests.entrySet()
+                    .stream()
+                    .map(entry -> (Runnable) () -> {
+                        java.io.File translationFile = entry.getKey();
+                        List<Language> langs = entry.getValue().getLeft();
+                        UploadTranslationsStringsRequest request = entry.getValue().getRight();
+                        try (InputStream fileStream = new FileInputStream(translationFile)) {
+                            Long storageId = client.uploadStorage(translationFile.getName(), fileStream);
+                            request.setStorageId(storageId);
+                        } catch (Exception e) {
+                            containsErrors.set(true);
+                            throw new RuntimeException(String.format(
+                                RESOURCE_BUNDLE.getString("error.upload_translation_to_storage"),
+                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
+                            ), e);
+                        }
+                        try {
+                            for (Language lang : langs) {
+                                client.uploadTranslationStringsBased(lang.getId(), request);
                             }
+                        } catch (Exception e) {
+                            containsErrors.set(true);
+                            throw new RuntimeException(String.format(
+                                RESOURCE_BUNDLE.getString("error.upload_translation"),
+                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
+                            ), e);
                         }
-                    } catch (Exception e) {
-                        containsErrors.set(true);
-                        throw new RuntimeException(String.format(
-                            RESOURCE_BUNDLE.getString("error.upload_translation"),
-                            StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
-                        ), e);
-                    }
-                    if (!plainView) {
-                        out.println(OK.withIcon(String.format(
-                            RESOURCE_BUNDLE.getString("message.translation_uploaded"),
-                            StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()))));
-                    } else {
-                        out.println(StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()));
-                    }
-                })
-                .collect(Collectors.toList());
+                        if (!plainView) {
+                            out.println(OK.withIcon(String.format(
+                                RESOURCE_BUNDLE.getString("message.translation_uploaded"),
+                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()))));
+                        } else {
+                            out.println(StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath()));
+                        }
+                    })
+                    .collect(Collectors.toList());
+            }
             ConcurrencyUtil.executeAndWait(tasks, debug);
 
             if (containsErrors.get()) {
diff --git a/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java b/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java
index 51715fe5d..e3eb1a9bf 100644
--- a/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java
+++ b/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java
@@ -12,6 +12,7 @@
 import com.crowdin.client.labels.model.AddLabelRequest;
 import com.crowdin.client.sourcefiles.model.AddBranchRequest;
 import com.crowdin.client.sourcestrings.model.AddSourceStringRequest;
+import com.crowdin.client.sourcestrings.model.AddSourceStringStringsBasedRequest;
 import com.crowdin.client.stringcomments.model.AddStringCommentRequest;
 import com.crowdin.client.stringcomments.model.Type;
 import com.crowdin.client.tasks.model.CreateTaskRequest;
@@ -20,14 +21,7 @@
 import com.crowdin.client.translationmemory.model.TranslationMemoryExportRequest;
 import com.crowdin.client.translationmemory.model.TranslationMemoryFormat;
 import com.crowdin.client.translationmemory.model.TranslationMemoryImportRequest;
-import com.crowdin.client.translations.model.ApplyPreTranslationRequest;
-import com.crowdin.client.translations.model.AutoApproveOption;
-import com.crowdin.client.translations.model.CharTransformation;
-import com.crowdin.client.translations.model.CrowdinTranslationCraeteProjectPseudoBuildForm;
-import com.crowdin.client.translations.model.CrowdinTranslationCreateProjectBuildForm;
-import com.crowdin.client.translations.model.ExportProjectTranslationRequest;
-import com.crowdin.client.translations.model.Method;
-import com.crowdin.client.translations.model.UploadTranslationsRequest;
+import com.crowdin.client.translations.model.*;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,6 +42,18 @@ public static AddSourceStringRequest addString(String text, String identifier, I
         return request;
     }
 
+    public static AddSourceStringStringsBasedRequest addStringStringsBased(String text, String identifier, Integer maxLength, String context, Long branchId, Boolean hidden, List<Long> labelIds) {
+        AddSourceStringStringsBasedRequest request = new AddSourceStringStringsBasedRequest();
+        request.setText(text);
+        request.setIdentifier(identifier);
+        request.setMaxLength(maxLength);
+        request.setContext(context);
+        request.setBranchId(branchId);
+        request.setIsHidden(hidden);
+        request.setLabelIds(labelIds);
+        return request;
+    }
+
     public static AddStringCommentRequest addComment(String text, String type, String language, String issueType,
                                                      String stringId) {
         AddStringCommentRequest request = new AddStringCommentRequest();
@@ -285,6 +291,23 @@ public static ApplyPreTranslationRequest applyPreTranslation(
         return request;
     }
 
+    public static ApplyPreTranslationStringsBasedRequest applyPreTranslationStringsBased(
+        List<String> languageIds, List<Long> branchId, Method method, Long engineId, AutoApproveOption autoApproveOption,
+        Boolean duplicateTranslations, Boolean translateUntranslatedOnly, Boolean translateWithPerfectMatchOnly, List<Long> labelIds
+    ) {
+        ApplyPreTranslationStringsBasedRequest request = new ApplyPreTranslationStringsBasedRequest();
+        request.setLanguageIds(languageIds);
+        request.setBranchIds(branchId);
+        request.setMethod(method);
+        request.setEngineId(engineId);
+        request.setAutoApproveOption(autoApproveOption);
+        request.setDuplicateTranslations(duplicateTranslations);
+        request.setTranslateUntranslatedOnly(translateUntranslatedOnly);
+        request.setTranslateWithPerfectMatchOnly(translateWithPerfectMatchOnly);
+        request.setLabelIds(labelIds);
+        return request;
+    }
+
     public static AddBranchRequest addBranch(String name, String title, String exportPattern, Priority priority) {
         AddBranchRequest request = new AddBranchRequest();
         request.setName(name);
diff --git a/src/main/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommand.java
index 198a1c988..2cb4325f9 100644
--- a/src/main/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommand.java
+++ b/src/main/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommand.java
@@ -56,10 +56,6 @@ protected List<String> checkOptions() {
             errors.add(RESOURCE_BUNDLE.getString("error.distribution.empty_name"));
         }
 
-        if (exportMode == DEFAULT && files == null) {
-            errors.add(RESOURCE_BUNDLE.getString("error.distribution.empty_file"));
-        }
-
         if (exportMode == BUNDLE && bundleIds == null) {
             errors.add(RESOURCE_BUNDLE.getString("error.distribution.empty_bundle_ids"));
         }
diff --git a/src/main/java/com/crowdin/cli/commands/picocli/DistributionReleaseSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/DistributionReleaseSubcommand.java
index c14cc1b4b..3232ef2f9 100644
--- a/src/main/java/com/crowdin/cli/commands/picocli/DistributionReleaseSubcommand.java
+++ b/src/main/java/com/crowdin/cli/commands/picocli/DistributionReleaseSubcommand.java
@@ -1,8 +1,10 @@
 package com.crowdin.cli.commands.picocli;
 
 import com.crowdin.cli.client.ClientDistribution;
+import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.commands.Actions;
 import com.crowdin.cli.commands.NewAction;
+import com.crowdin.cli.commands.Outputter;
 import com.crowdin.cli.properties.ProjectProperties;
 import picocli.CommandLine;
 
@@ -20,6 +22,8 @@ class DistributionReleaseSubcommand extends ActCommandDistribution {
 
     @Override
     protected NewAction<ProjectProperties, ClientDistribution> getAction(Actions actions) {
-        return actions.distributionRelease(noProgress, plainView, hash);
+        Outputter out = new PicocliOutputter(System.out, isAnsi());
+        ProjectClient projectClient = this.getProjectClient(this.getProperties(propertiesBuilders, out));
+        return actions.distributionRelease(noProgress, plainView, hash, projectClient);
     }
 }
diff --git a/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java
index 0be2193a7..0b6f0faa9 100644
--- a/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java
+++ b/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java
@@ -35,6 +35,9 @@ class StringAddSubcommand extends ActCommandProject {
     @CommandLine.Option(names = {"--label"}, descriptionKey = "params.label", paramLabel = "...", order = -2)
     protected List<String> labelNames;
 
+    @CommandLine.Option(names = {"--branch"}, descriptionKey = "branch", paramLabel = "...", order = -2)
+    protected String branch;
+
     @CommandLine.Option(names = {"--hidden"}, order = -2)
     protected Boolean isHidden;
 
@@ -55,6 +58,6 @@ protected List<String> checkOptions() {
 
     @Override
     protected NewAction<ProjectProperties, ProjectClient> getAction(Actions actions) {
-        return actions.stringAdd(noProgress, text, identifier, maxLength, context, files, labelNames, isHidden);
+        return actions.stringAdd(noProgress, text, identifier, maxLength, context, files, labelNames, branch, isHidden);
     }
 }
diff --git a/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java b/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java
index c3fa94c98..fa8800d13 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/CliActionsTest.java
@@ -49,7 +49,7 @@ public void testStatus() {
 
     @Test
     public void testStringAdd() {
-        assertNotNull(actions.stringAdd(false, null, null, null, null, null, null, null));
+        assertNotNull(actions.stringAdd(false, null, null, null, null, null, null, null, null));
     }
 
     @Test
@@ -119,7 +119,7 @@ public void testDistributionList() {
 
     @Test
     public void testDistributionRelease() {
-        assertNotNull(actions.distributionRelease(false,true,null));
+        assertNotNull(actions.distributionRelease(false,true,null, null));
     }
 
     @Test
diff --git a/src/test/java/com/crowdin/cli/commands/actions/DistributionAddActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/DistributionAddActionTest.java
index d97021a1c..bc149f075 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/DistributionAddActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/DistributionAddActionTest.java
@@ -1,6 +1,7 @@
 package com.crowdin.cli.commands.actions;
 
 import com.crowdin.cli.client.ClientDistribution;
+import com.crowdin.cli.client.CrowdinProjectFull;
 import com.crowdin.cli.client.ProjectBuilder;
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.commands.NewAction;
@@ -13,8 +14,10 @@
 import com.crowdin.cli.properties.helper.TempProject;
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.client.distributions.model.AddDistributionRequest;
+import com.crowdin.client.distributions.model.AddDistributionStringsBasedRequest;
 import com.crowdin.client.distributions.model.Distribution;
 import com.crowdin.client.distributions.model.ExportMode;
+import com.crowdin.client.projectsgroups.model.Type;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -68,8 +71,10 @@ public void testDistributionAdd(String name, ExportMode exportMode, List<String>
                                             "/%original_file_name%-CR-%locale%")));
 
         ProjectClient projectClient = mock(ProjectClient.class);
+        CrowdinProjectFull build = projectBuilder.build();
+        build.setType(Type.FILES_BASED);
         when(projectClient.downloadFullProject(branch))
-                .thenReturn(projectBuilder.build());
+                .thenReturn(build);
 
         action = new DistributionAddAction(true, true, name, exportMode, files, bundleIds, branch, projectClient);
         action.act(Outputter.getDefault(), pb, client);
@@ -105,4 +110,38 @@ public void testAddDistributionThrows() {
         verifyNoMoreInteractions(client);
     }
 
+    @Test
+    public void testDistributionAdd_StringsBasedProject() {
+        TempProject project = new TempProject(FileHelperTest.class);
+
+        NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
+            .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
+            .setBasePath(project.getBasePath());
+        PropertiesWithFiles pb = pbBuilder.build();
+
+        AddDistributionStringsBasedRequest request = new AddDistributionStringsBasedRequest();
+        request.setName("My Distribution 1");
+        request.setBundleIds(Arrays.asList(9));
+
+        ClientDistribution client = mock(ClientDistribution.class);
+        when(client.addDistributionStringsBased(request))
+            .thenReturn(new Distribution() {{
+                setName(request.getName());
+                setBundleIds(request.getBundleIds());
+            }});
+
+        ProjectBuilder projectBuilder = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()));
+        projectBuilder.addBranches(1L, "main");
+        ProjectClient projectClient = mock(ProjectClient.class);
+        CrowdinProjectFull build = projectBuilder.build();
+        build.setType(Type.STRINGS_BASED);
+        when(projectClient.downloadFullProject("main"))
+            .thenReturn(build);
+
+        action = new DistributionAddAction(true, true, "My Distribution 1", ExportMode.BUNDLE, null, Arrays.asList(9), "main", projectClient);
+        action.act(Outputter.getDefault(), pb, client);
+        verify(client).addDistributionStringsBased(request);
+        verifyNoMoreInteractions(client);
+    }
+
 }
diff --git a/src/test/java/com/crowdin/cli/commands/actions/DistributionReleaseActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/DistributionReleaseActionTest.java
index dd7f926bd..159efa812 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/DistributionReleaseActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/DistributionReleaseActionTest.java
@@ -1,6 +1,9 @@
 package com.crowdin.cli.commands.actions;
 
 import com.crowdin.cli.client.ClientDistribution;
+import com.crowdin.cli.client.CrowdinProjectInfo;
+import com.crowdin.cli.client.ProjectBuilder;
+import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.commands.NewAction;
 import com.crowdin.cli.commands.Outputter;
 import com.crowdin.cli.properties.NewPropertiesWithFilesUtilBuilder;
@@ -8,6 +11,8 @@
 import com.crowdin.cli.properties.PropertiesWithFiles;
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.client.distributions.model.DistributionRelease;
+import com.crowdin.client.distributions.model.DistributionStringsBasedRelease;
+import com.crowdin.client.projectsgroups.model.Type;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -26,11 +31,17 @@ public void releaseDistributionTest_whenSuccess() {
                 .setBasePath(Utils.PATH_SEPARATOR);
         PropertiesWithFiles pb = pbBuilder.build();
         ClientDistribution client = mock(ClientDistribution.class);
+        ProjectClient projectClient = mock(ProjectClient.class);
+        CrowdinProjectInfo projectInfo = mock(CrowdinProjectInfo.class);
+        when(projectInfo.getType())
+            .thenReturn(Type.FILES_BASED);
+        when(projectClient.downloadProjectInfo())
+            .thenReturn(projectInfo);
         DistributionRelease distributionRelease = new DistributionRelease();
         distributionRelease.setStatus("success");
         when(client.release(HASH)).thenReturn(distributionRelease);
 
-        action = new DistributionReleaseAction(true, true, HASH);
+        action = new DistributionReleaseAction(true, true, HASH, projectClient);
         action.act(Outputter.getDefault(), pb, client);
         verify(client).release(HASH);
     }
@@ -42,14 +53,42 @@ public void releaseDistributionTest_whenFailed() {
                 .setBasePath(Utils.PATH_SEPARATOR);
         PropertiesWithFiles pb = pbBuilder.build();
         ClientDistribution client = mock(ClientDistribution.class);
+        ProjectClient projectClient = mock(ProjectClient.class);
+        CrowdinProjectInfo projectInfo = mock(CrowdinProjectInfo.class);
+        when(projectInfo.getType())
+            .thenReturn(Type.FILES_BASED);
+        when(projectClient.downloadProjectInfo())
+            .thenReturn(projectInfo);
         DistributionRelease distributionRelease = new DistributionRelease();
         distributionRelease.setStatus("failed");
         when(client.release(any())).thenReturn(distributionRelease);
         when(client.getDistributionRelease(eq(HASH))).thenReturn(distributionRelease);
 
-        action = new DistributionReleaseAction(true, true, HASH);
+        action = new DistributionReleaseAction(true, true, HASH, projectClient);
         assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client));
         verify(client).release(HASH);
         verify(client).getDistributionRelease(HASH);
     }
+
+    @Test
+    public void releaseDistributionTest_StringsBasedProject() {
+        NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
+            .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
+            .setBasePath(Utils.PATH_SEPARATOR);
+        PropertiesWithFiles pb = pbBuilder.build();
+        ClientDistribution client = mock(ClientDistribution.class);
+        ProjectClient projectClient = mock(ProjectClient.class);
+        CrowdinProjectInfo projectInfo = mock(CrowdinProjectInfo.class);
+        when(projectInfo.getType())
+            .thenReturn(Type.STRINGS_BASED);
+        when(projectClient.downloadProjectInfo())
+            .thenReturn(projectInfo);
+        DistributionStringsBasedRelease distributionRelease = new DistributionStringsBasedRelease();
+        distributionRelease.setStatus("success");
+        when(client.releaseStringsBased(HASH)).thenReturn(distributionRelease);
+
+        action = new DistributionReleaseAction(true, true, HASH, projectClient);
+        action.act(Outputter.getDefault(), pb, client);
+        verify(client).releaseStringsBased(HASH);
+    }
 }
diff --git a/src/test/java/com/crowdin/cli/commands/actions/StringAddActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/StringAddActionTest.java
index f1ba8f845..9e701368d 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/StringAddActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/StringAddActionTest.java
@@ -1,5 +1,6 @@
 package com.crowdin.cli.commands.actions;
 
+import com.crowdin.cli.client.CrowdinProjectFull;
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.client.ProjectBuilder;
 import com.crowdin.cli.client.ResponseException;
@@ -11,7 +12,9 @@
 import com.crowdin.cli.properties.NewPropertiesWithFilesUtilBuilder;
 import com.crowdin.cli.utils.Utils;
 import com.crowdin.client.labels.model.Label;
+import com.crowdin.client.projectsgroups.model.Type;
 import com.crowdin.client.sourcestrings.model.AddSourceStringRequest;
+import com.crowdin.client.sourcestrings.model.AddSourceStringStringsBasedRequest;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -40,7 +43,7 @@ public class StringAddActionTest {
     @MethodSource
     public void testStringAdd(
         String text, String identifier, Integer maxLength, String context, List<Long> labelIds, List<String> labelNames, Boolean hidden, Map<String, Long> files, String[] stringFiles
-    ) throws ResponseException {
+    ) {
         NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
             .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
             .setBasePath(Utils.PATH_SEPARATOR);
@@ -67,11 +70,13 @@ public void testStringAdd(
         }
 
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = projectBuilder.build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(projectBuilder.build());
+            .thenReturn(build);
 
         action =
-            new StringAddAction(true, text, identifier, maxLength, context, Arrays.asList(stringFiles), labelNames, hidden);
+            new StringAddAction(true, text, identifier, maxLength, context, Arrays.asList(stringFiles), labelNames, null, hidden);
         action.act(Outputter.getDefault(), pb, client);
 
         verify(client).downloadFullProject();
@@ -131,11 +136,13 @@ public void testStringAdd_throwsNotFound() {
         }
 
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = projectBuilder.build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(projectBuilder.build());
+            .thenReturn(build);
 
         action =
-            new StringAddAction(true, text, identifier, maxLength, context, Arrays.asList(stringFiles), labelNames, hidden);
+            new StringAddAction(true, text, identifier, maxLength, context, Arrays.asList(stringFiles), labelNames, null, hidden);
         assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client));
 
         verify(client).downloadFullProject();
@@ -157,7 +164,7 @@ public void testGetProjectThrows() throws ResponseException {
         when(client.downloadFullProject())
             .thenThrow(new RuntimeException("Whoops"));
 
-        action = new StringAddAction(false, null, null, null, null, null, null, null);
+        action = new StringAddAction(false, null, null, null, null, null, null, null, null);
         assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client));
 
         verify(client).downloadFullProject();
@@ -205,8 +212,10 @@ public void testStringAdd_UseLabels() {
         }
 
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = projectBuilder.build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(projectBuilder.build());
+            .thenReturn(build);
         List<Label> labelsResponse = labels.entrySet().stream()
             .map(entry -> createLabel(entry.getKey(), entry.getValue()))
             .collect(Collectors.toList());
@@ -215,7 +224,7 @@ public void testStringAdd_UseLabels() {
 
 
         action =
-            new StringAddAction(true, text, identifier, maxLength, context, Arrays.asList(stringFiles), new ArrayList<>(labels.values()), hidden);
+            new StringAddAction(true, text, identifier, maxLength, context, Arrays.asList(stringFiles), new ArrayList<>(labels.values()), null, hidden);
         action.act(Outputter.getDefault(), pb, client);
 
         verify(client).downloadFullProject();
@@ -233,4 +242,31 @@ private Label createLabel(Long id, String title) {
         label.setTitle(title);
         return label;
     }
+
+    @Test
+    public void testStringAdd_StringsBasedProject() {
+        NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
+            .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
+            .setBasePath(Utils.PATH_SEPARATOR);
+        PropertiesWithFiles pb = pbBuilder.build();
+        AddSourceStringStringsBasedRequest request = RequestBuilder.addStringStringsBased(
+            "first text", "1.1", 42, "It's first text", 1L, false, null);
+
+        ProjectBuilder projectBuilder = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).addBranches(1L, "main");
+
+        ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = projectBuilder.build();
+        build.setType(Type.STRINGS_BASED);
+        when(client.downloadFullProject())
+            .thenReturn(build);
+
+        action =
+            new StringAddAction(true, "first text", "1.1", 42, "It's first text", null, null, "main", false);
+        action.act(Outputter.getDefault(), pb, client);
+
+        verify(client).downloadFullProject();
+        verify(client).addSourceStringStringsBased(request);
+
+        verifyNoMoreInteractions(client);
+    }
 }
diff --git a/src/test/java/com/crowdin/cli/commands/actions/StringListActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/StringListActionTest.java
index 6b455ef57..ca8690978 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/StringListActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/StringListActionTest.java
@@ -1,5 +1,6 @@
 package com.crowdin.cli.commands.actions;
 
+import com.crowdin.cli.client.CrowdinProjectFull;
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.client.ProjectBuilder;
 import com.crowdin.cli.client.ResponseException;
@@ -10,6 +11,7 @@
 import com.crowdin.cli.properties.PropertiesWithFiles;
 import com.crowdin.cli.properties.NewPropertiesWithFilesUtilBuilder;
 import com.crowdin.cli.utils.Utils;
+import com.crowdin.client.projectsgroups.model.Type;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -38,9 +40,11 @@ public void testStringList(String file, String filter) throws ResponseException
             .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
             .setBasePath(Utils.PATH_SEPARATOR);
         pb = pbBuilder.build();
+        CrowdinProjectFull projectFull = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("first.csv", "csv", 101L, null, null).build();
+        projectFull.setType(Type.FILES_BASED);
         when(client.downloadFullProject(null))
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addFile("first.csv", "csv", 101L, null, null).build());
+            .thenReturn(projectFull);
         when(client.listSourceString(101L, null, null, filter, null))
             .thenReturn(Arrays.asList(SourceStringBuilder.standard()
                 .setProjectId(Long.parseLong(pb.getProjectId()))
@@ -101,4 +105,29 @@ public void testFileNotExistThrows() throws ResponseException {
         verify(client).listLabels();
         verifyNoMoreInteractions(client);
     }
+
+    @Test
+    public void testStringList_StringsBasedProject() {
+        NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
+            .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
+            .setBasePath(Utils.PATH_SEPARATOR);
+        pb = pbBuilder.build();
+        CrowdinProjectFull projectFull = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("first.csv", "csv", 101L, null, null).build();
+        projectFull.setType(Type.STRINGS_BASED);
+        when(client.downloadFullProject(null))
+            .thenReturn(projectFull);
+        when(client.listSourceString(101L, null, null, null, null))
+            .thenReturn(Arrays.asList(SourceStringBuilder.standard()
+                .setProjectId(Long.parseLong(pb.getProjectId()))
+                .setIdentifiers(701L, "7-0-1", "seven-o-one", "7.0.1", 101L).build()));
+
+        action = new StringListAction(true, true, null, null, null, null);
+        action.act(Outputter.getDefault(), pb, client);
+
+        verify(client).downloadFullProject(null);
+        verify(client).listLabels();
+        verify(client).listSourceString(null, null, null, null, null);
+        verifyNoMoreInteractions(client);
+    }
 }
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 fc2519891..3624790ee 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/UploadSourcesActionTest.java
@@ -1,5 +1,6 @@
 package com.crowdin.cli.commands.actions;
 
+import com.crowdin.cli.client.CrowdinProjectFull;
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.client.ProjectBuilder;
 import com.crowdin.cli.client.ResponseException;
@@ -12,6 +13,7 @@
 import com.crowdin.cli.properties.helper.FileHelperTest;
 import com.crowdin.cli.properties.helper.TempProject;
 import com.crowdin.cli.utils.Utils;
+import com.crowdin.client.projectsgroups.model.Type;
 import com.crowdin.client.sourcefiles.model.AddBranchRequest;
 import com.crowdin.client.sourcefiles.model.AddDirectoryRequest;
 import com.crowdin.client.sourcefiles.model.AddFileRequest;
@@ -24,6 +26,8 @@
 import com.crowdin.client.sourcefiles.model.PropertyFileExportOptions;
 import com.crowdin.client.sourcefiles.model.SpreadsheetFileImportOptions;
 import com.crowdin.client.sourcefiles.model.UpdateFileRequest;
+import com.crowdin.client.sourcestrings.model.ImportOptions;
+import com.crowdin.client.sourcestrings.model.UploadStringsRequest;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -61,8 +65,10 @@ public void testUploadOneSource_EmptyProject() throws ResponseException {
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
 
@@ -98,8 +104,10 @@ public void testUploadOneSourceWithNullContentSegmentation_EmptyProject() throws
         pb.getFiles().get(0).setContentSegmentation(null);
 
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-                .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+                .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
                 .thenReturn(1L);
 
@@ -135,8 +143,10 @@ public void testUploadPropertiesFileWithEscapeQuotes_EmptyProject() throws Respo
         pb.getFiles().get(0).setEscapeQuotes(1);
 
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-          .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+          .thenReturn(build);
         when(client.uploadStorage(eq("first.properties"), any()))
           .thenReturn(1L);
 
@@ -174,8 +184,10 @@ public void testUploadJavaScriptFileWithExportQuotes_EmptyProject() throws Respo
         pb.getFiles().get(0).setExportQuotes("single");
 
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-          .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+          .thenReturn(build);
         when(client.uploadStorage(eq("first.js"), any()))
           .thenReturn(1L);
 
@@ -212,8 +224,10 @@ public void testUploadFewSourceWithDirectories_EmptyProject() throws ResponseExc
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
         AddDirectoryRequest addDirectoryRequest = new AddDirectoryRequest() {{
@@ -288,8 +302,10 @@ public void testUploadOneSourceWithBranch_EmptyProject() throws ResponseExceptio
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
         Branch branch = BranchBuilder.standard().setProjectId(Long.parseLong(pb.getProjectId()))
@@ -332,9 +348,11 @@ public void testUploadOneSourceWithBranch_ProjectWithThatBranch() throws Respons
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addBranches(201L, "newBranch").build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addBranches(201L, "newBranch").build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
 
@@ -369,8 +387,10 @@ public void testUploadOneSourceWithDirectory_ProjectNotPreserveHierarchy() throw
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
 
@@ -406,9 +426,11 @@ public void testUploadOneSourceWithDirectory_ProjectWithPreserveHierarchy() thro
         PropertiesWithFiles pb = pbBuilder.build();
         pb.setPreserveHierarchy(true);
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addDirectory("folder", 101L, null, null).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addDirectory("folder", 101L, null, null).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
 
@@ -444,8 +466,10 @@ public void testUploadOneSourceWithDest_Project() throws ResponseException {
         PropertiesWithFiles pb = pbBuilder.build();
         pb.getFiles().get(0).setDest("last.po");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
 
@@ -484,15 +508,17 @@ public void testUploadOneSourceWithDestAndDeleteObsoleteOption_Project() throws
         pb.setPreserveHierarchy(true);
         pb.setProjectId("551261");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("locale.yml", "yaml", 66l, 209l, null, translationPattern)
+            .addFile("index.md", "yaml", 77l, 209l, null, translationPattern)
+            .addDirectory("docs", 207l, 215l, null)
+            .addDirectory("app", 215l, null, null)
+            .addDirectory("en", 209l, 207l, null)
+            .addDirectory("en", 225l, null, null)
+            .build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-                .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                        .addFile("locale.yml", "yaml", 66l, 209l, null, translationPattern)
-                        .addFile("index.md", "yaml", 77l, 209l, null, translationPattern)
-                        .addDirectory("docs", 207l, 215l, null)
-                        .addDirectory("app", 215l, null, null)
-                        .addDirectory("en", 209l, 207l, null)
-                        .addDirectory("en", 225l, null, null)
-                        .build());
+                .thenReturn(build);
         when(client.uploadStorage(eq("/docs/en/index.md"), any()))
                 .thenReturn(1L);
 
@@ -526,9 +552,11 @@ public void testUpdateOneUploadOneSource_Project() throws ResponseException {
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("first.po", "gettext", 101L, null, null).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addFile("first.po", "gettext", 101L, null, null).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
         when(client.uploadStorage(eq("second.po"), any()))
@@ -579,8 +607,10 @@ public void testAddCsvFile_EmptyProject() throws ResponseException {
         PropertiesWithFiles pb = pbBuilder.build();
         pb.getFiles().get(0).setScheme("identifier,source_phrase,context,uk,ru,fr");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-                .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+                .thenReturn(build);
         when(client.uploadStorage(eq("first.csv"), any()))
                 .thenReturn(1L);
 
@@ -623,8 +653,10 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_1_Project() throws
         PropertiesWithFiles pb = pbBuilder.build();
         pb.getFiles().get(0).setDest("%file_name%_file.%file_extension%");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
 
@@ -660,8 +692,10 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_2_Project() throws
         PropertiesWithFiles pb = pbBuilder.build();
         pb.getFiles().get(0).setDest("**/%original_file_name%");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
         AddDirectoryRequest addDirectoryRequest1 = new AddDirectoryRequest() {{
@@ -705,8 +739,10 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_3_Project() throws
         PropertiesWithFiles pb = pbBuilder.build();
         pb.getFiles().get(0).setDest("%original_path%/inner/%original_file_name%");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject())
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po"), any()))
             .thenReturn(1L);
         AddDirectoryRequest addDirectoryRequest1 = new AddDirectoryRequest() {{
@@ -749,4 +785,35 @@ public void testUploadOneSourceWithDest_DifferentPlaceholders_3_Project() throws
         verify(client).addSource(eq(addFileRequest));
         verifyNoMoreInteractions(client);
     }
+
+    @Test
+    public void testUploadOneSource_StringBasedProject() throws ResponseException {
+        project.addFile(Utils.normalizePath("first.po"), "Hello, World!");
+        NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
+            .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%")
+            .setBasePath(project.getBasePath());
+        PropertiesWithFiles pb = pbBuilder.build();
+        ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).addBranches(2L, "main").build();
+        build.setType(Type.STRINGS_BASED);
+        when(client.downloadFullProject())
+            .thenReturn(build);
+        when(client.uploadStorage(eq("first.po"), any()))
+            .thenReturn(1L);
+
+        NewAction<PropertiesWithFiles, ProjectClient> action = new UploadSourcesAction("main", false, false, true, false, false);
+        action.act(Outputter.getDefault(), pb, client);
+
+        verify(client).downloadFullProject();
+        verify(client).listLabels();
+        verify(client).uploadStorage(eq("first.po"), any());
+        UploadStringsRequest uploadStringsRequest = new UploadStringsRequest() {
+            {
+                setStorageId(1L);
+                setBranchId(2L);
+                setImportOptions(new ImportOptions());
+        }};
+        verify(client).addSourceStringsBased(eq(uploadStringsRequest));
+        verifyNoMoreInteractions(client);
+    }
 }
diff --git a/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java
index 09f68a4ed..96aadd992 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java
@@ -1,5 +1,6 @@
 package com.crowdin.cli.commands.actions;
 
+import com.crowdin.cli.client.CrowdinProjectFull;
 import com.crowdin.cli.client.ProjectClient;
 import com.crowdin.cli.client.ProjectBuilder;
 import com.crowdin.cli.client.ResponseException;
@@ -10,7 +11,9 @@
 import com.crowdin.cli.properties.helper.FileHelperTest;
 import com.crowdin.cli.properties.helper.TempProject;
 import com.crowdin.cli.utils.Utils;
+import com.crowdin.client.projectsgroups.model.Type;
 import com.crowdin.client.translations.model.UploadTranslationsRequest;
+import com.crowdin.client.translations.model.UploadTranslationsStringsRequest;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -49,9 +52,11 @@ public void testUploadOneOfTwoTranslation_Project() throws ResponseException {
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("first.po", "gettext", 301L, null, null).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject(null))
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addFile("first.po", "gettext", 301L, null, null).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po-CR-uk-UA"), any()))
             .thenReturn(1L);
 
@@ -81,9 +86,11 @@ public void testUploadBothTranslation_Project() throws ResponseException {
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("first.po", "gettext", 301L, null, null).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject(null))
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addFile("first.po", "gettext", 301L, null, null).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po-CR-uk-UA"), any()))
             .thenReturn(1L);
         when(client.uploadStorage(eq("first.po-CR-ru-RU"), any()))
@@ -123,8 +130,10 @@ public void testUploadOneOfTwoTranslation_EmptyProject() throws ResponseExceptio
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject(null))
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).build());
+            .thenReturn(build);
 
         NewAction<PropertiesWithFiles, ProjectClient> action = new UploadTranslationsAction(false, null, null, false, false, false, false, false);
         assertThrows(RuntimeException.class, () -> action.act(Outputter.getDefault(), pb, client));
@@ -143,9 +152,11 @@ public void testUploadSpreadsheetTranslation_Project() throws ResponseException
         PropertiesWithFiles pb = pbBuilder.build();
         pb.getFiles().get(0).setScheme("identifier,source_phrase,context,uk,ru,fr");
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("first.csv", "csv", 301L, null, null).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject(null))
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addFile("first.csv", "csv", 301L, null, null).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.csv-CR"), any()))
             .thenReturn(1L);
 
@@ -175,9 +186,11 @@ public void testUploadWithDest() throws ResponseException {
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
+            .addFile("second.po", "gettext", 301L, null, null).build();
+        build.setType(Type.FILES_BASED);
         when(client.downloadFullProject(null))
-            .thenReturn(ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId()))
-                .addFile("second.po", "gettext", 301L, null, null).build());
+            .thenReturn(build);
         when(client.uploadStorage(eq("first.po-CR-uk-UA"), any()))
             .thenReturn(1L);
 
@@ -196,4 +209,36 @@ public void testUploadWithDest() throws ResponseException {
         verify(client).uploadTranslations(eq("ua"), eq(uploadTranslationRequest));
         verifyNoMoreInteractions(client);
     }
+
+    @Test
+    public void testUploadOneOfTwoTranslation_StringBasedProject() throws ResponseException {
+        project.addFile(Utils.normalizePath("first.po"), "Hello, World!");
+        project.addFile(Utils.normalizePath("translation.po"), "Hello, World!");
+        NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
+            .minimalBuiltPropertiesBean("*", "translation.po")
+            .setBasePath(project.getBasePath());
+        PropertiesWithFiles pb = pbBuilder.build();
+        ProjectClient client = mock(ProjectClient.class);
+        CrowdinProjectFull build = ProjectBuilder.emptyProject(Long.parseLong(pb.getProjectId())).addBranches(2L, "main").build();
+        build.setType(Type.STRINGS_BASED);
+        when(client.downloadFullProject("main"))
+            .thenReturn(build);
+        when(client.uploadStorage(eq("translation.po"), any()))
+            .thenReturn(1L);
+
+        NewAction<PropertiesWithFiles, ProjectClient> action = new UploadTranslationsAction(false, null, "main", false, false, false, false, false);
+        assertDoesNotThrow(() -> action.act(Outputter.getDefault(), pb, client));
+
+        verify(client).downloadFullProject("main");
+        verify(client).uploadStorage(eq("translation.po"), any());
+        UploadTranslationsStringsRequest uploadTranslationRequest = new UploadTranslationsStringsRequest() {{
+            setStorageId(1L);
+            setBranchId(2L);
+            setImportEqSuggestions(false);
+            setAutoApproveImported(false);
+            setTranslateHidden(false);
+        }};
+        verify(client).uploadTranslationStringsBased(eq("ua"), eq(uploadTranslationRequest));
+        verifyNoMoreInteractions(client);
+    }
 }
diff --git a/src/test/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommandTest.java b/src/test/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommandTest.java
index 1752788b3..6edb8ea92 100644
--- a/src/test/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommandTest.java
+++ b/src/test/java/com/crowdin/cli/commands/picocli/DistributionAddSubcommandTest.java
@@ -60,10 +60,9 @@ public void testSubCommandCheckInvalidOptions(String name, ExportMode exportMode
 
     public static Stream<Arguments> testSubCommandCheckInvalidOptions() {
         return Stream.of(
-            arguments("Distribution 1", ExportMode.DEFAULT, null, null, Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.empty_file"))),
-            arguments("Distribution 2", ExportMode.BUNDLE, null, null, Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.empty_bundle_ids"))),
-            arguments("Distribution 3", ExportMode.BUNDLE, Arrays.asList("file.json"), Arrays.asList(1l), Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.incorrect_file_command_usage"))),
-            arguments("Distribution 4", ExportMode.DEFAULT, Arrays.asList("file.json"), Arrays.asList(1l), Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.incorrect_bundle_id_command_usage")))
+            arguments("Distribution 1", ExportMode.BUNDLE, null, null, Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.empty_bundle_ids"))),
+            arguments("Distribution 2", ExportMode.BUNDLE, Arrays.asList("file.json"), Arrays.asList(1l), Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.incorrect_file_command_usage"))),
+            arguments("Distribution 3", ExportMode.DEFAULT, Arrays.asList("file.json"), Arrays.asList(1l), Arrays.asList(RESOURCE_BUNDLE.getString("error.distribution.incorrect_bundle_id_command_usage")))
         );
     }
 }
diff --git a/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java b/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java
index 88238549c..b9f4b566f 100644
--- a/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java
+++ b/src/test/java/com/crowdin/cli/commands/picocli/PicocliTestUtils.java
@@ -72,7 +72,7 @@ void mockActions() {
             .thenReturn(actionMock);
         when(actionsMock.status(anyBoolean(), any(), any(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean()))
             .thenReturn(actionMock);
-        when(actionsMock.stringAdd(anyBoolean(), any(), any(), any(), any(), any(), any(), any()))
+        when(actionsMock.stringAdd(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), any()))
             .thenReturn(actionMock);
         when(actionsMock.stringComment(anyBoolean(), anyBoolean(), any(), any(), any(), any(), any()))
                 .thenReturn(actionMock);
diff --git a/src/test/java/com/crowdin/cli/commands/picocli/StringAddSubcommandTest.java b/src/test/java/com/crowdin/cli/commands/picocli/StringAddSubcommandTest.java
index 770e8e364..2d639955d 100644
--- a/src/test/java/com/crowdin/cli/commands/picocli/StringAddSubcommandTest.java
+++ b/src/test/java/com/crowdin/cli/commands/picocli/StringAddSubcommandTest.java
@@ -12,7 +12,7 @@ public class StringAddSubcommandTest extends PicocliTestUtils {
     public void testStringAdd() {
         this.execute(CommandNames.STRING, CommandNames.STRING_ADD, "\"Text\"", "--debug");
         verify(actionsMock)
-            .stringAdd(anyBoolean(), any(), any(), any(), any(), any(), any(), any());
+            .stringAdd(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), any());
         this.check(true);
     }
 
@@ -25,7 +25,7 @@ public void testStringAddInvalidOptions() {
     public void testStringAdd2() {
         this.execute(CommandNames.STRING, CommandNames.STRING_ADD, "\"Text\"", "--file", "path/to/file.txt");
         verify(actionsMock)
-            .stringAdd(anyBoolean(), any(), any(), any(), any(), any(), any(), any());
+            .stringAdd(anyBoolean(), any(), any(), any(), any(), any(), any(), any(), any());
         this.check(true);
     }
 }

From 67862cbbcf70aacc28a6839c3d6bcc64b9c762f7 Mon Sep 17 00:00:00 2001
From: Kateryna Oblakevych <malutina.catya14@gmail.com>
Date: Wed, 31 Jan 2024 08:34:04 +0200
Subject: [PATCH 2/5] fix upload strings action

---
 .../actions/DistributionReleaseAction.java    |   4 +-
 .../commands/actions/PreTranslateAction.java  |   6 +-
 .../actions/UploadTranslationsAction.java     | 115 ++++++++++--------
 .../functionality/RequestBuilder.java         |   9 ++
 .../commands/picocli/StringAddSubcommand.java |   2 +-
 .../cli/properties/PropertiesWithFiles.java   |   2 +-
 .../resources/messages/messages.properties    |   2 +
 .../actions/UploadTranslationsActionTest.java |   8 +-
 8 files changed, 83 insertions(+), 65 deletions(-)

diff --git a/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java b/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
index e03183ede..4fd7a6ad3 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
@@ -62,7 +62,7 @@ private DistributionRelease releaseDistributionFilesBased(Outputter out, ClientD
                         release = client.getDistributionRelease(hash);
 
                         if ("failed".equalsIgnoreCase(release.getStatus())) {
-                            throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
+                            throw new RuntimeException(RESOURCE_BUNDLE.getString("error.distribution_failed"));
                         }
                     }
 
@@ -92,7 +92,7 @@ private DistributionStringsBasedRelease releaseDistributionStringsBased(Outputte
                         release = client.getDistributionStringsBasedRelease(hash);
 
                         if ("failed".equalsIgnoreCase(release.getStatus())) {
-                            throw new RuntimeException(RESOURCE_BUNDLE.getString("message.spinner.build_has_failed"));
+                            throw new RuntimeException(RESOURCE_BUNDLE.getString("error.distribution_failed"));
                         }
                     }
 
diff --git a/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java b/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
index 39b723f8c..f82c161f9 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
@@ -79,10 +79,8 @@ public void act(Outputter out, PropertiesWithFiles properties, ProjectClient cli
                 duplicateTranslations, translateUntranslatedOnly, translateWithPerfectMatchOnly, labelIds);
             this.applyPreTranslation(out, client, request);
         } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
-            Branch branch = BranchUtils.getOrCreateBranch(out, branchName, client, project, false);
-            if (Objects.isNull(branch)) {
-                throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"));
-            }
+            Branch branch = project.findBranchByName(branchName)
+                .orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project")));
             ApplyPreTranslationStringsBasedRequest request = RequestBuilder.applyPreTranslationStringsBased(
                 languages, Collections.singletonList(branch.getId()), method, engineId, autoApproveOption,
                 duplicateTranslations, translateUntranslatedOnly, translateWithPerfectMatchOnly, labelIds);
diff --git a/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java b/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
index 8f6b8b392..5c86516e1 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
@@ -130,35 +130,14 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                     String translation = TranslationsUtils.replaceDoubleAsterisk(file.getSource(), file.getTranslation(), fileSource);
                     translation = placeholderUtil.replaceFileDependentPlaceholders(translation, new java.io.File(source));
                     if (file.getScheme() != null && !PlaceholderUtil.containsLangPlaceholders(translation)) {
-                        java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + translation);
-                        if (!transFile.exists()) {
-                            if (!plainView) {
-                                out.println(SKIPPED.withIcon(String.format(
-                                    RESOURCE_BUNDLE.getString("error.translation_not_exists"),
-                                    StringUtils.removeStart(transFile.getAbsolutePath(), pb.getBasePath()))));
-                            }
-                            return;
-                        }
+                        java.io.File transFile = getTranslationFile(out, pb, translation);
+                        if (Objects.isNull(transFile)) return;
                         UploadTranslationsRequest request = RequestBuilder.uploadTranslations(fileId, importEqSuggestions, autoApproveImported, translateHidden);
                         preparedRequests.put(transFile, Pair.of(languages, request));
                     } else {
                         for (Language language : languages) {
-                            LanguageMapping localLanguageMapping =
-                                LanguageMapping.fromConfigFileLanguageMapping(file.getLanguagesMapping());
-                            LanguageMapping languageMapping =
-                                LanguageMapping.populate(localLanguageMapping, serverLanguageMapping);
-
-                            String transFileName = placeholderUtil.replaceLanguageDependentPlaceholders(translation, languageMapping, language);
-                            transFileName = PropertiesBeanUtils.useTranslationReplace(transFileName, file.getTranslationReplace());
-                            java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + transFileName);
-                            if (!transFile.exists()) {
-                                if (!plainView) {
-                                    out.println(SKIPPED.withIcon(String.format(
-                                        RESOURCE_BUNDLE.getString("error.translation_not_exists"),
-                                        StringUtils.removeStart(transFile.getAbsolutePath(), pb.getBasePath()))));
-                                }
-                                continue;
-                            }
+                            java.io.File transFile = getTranslationFileWithPlaceholders(out, pb, placeholderUtil, serverLanguageMapping, file, translation, language);
+                            if (Objects.isNull(transFile)) continue;
                             UploadTranslationsRequest request = RequestBuilder.uploadTranslations(fileId, importEqSuggestions, autoApproveImported, translateHidden);
                             preparedRequests.put(transFile, Pair.of(Collections.singletonList(language), request));
                         }
@@ -171,16 +150,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                         java.io.File translationFile = entry.getKey();
                         List<Language> langs = entry.getValue().getLeft();
                         UploadTranslationsRequest request = entry.getValue().getRight();
-                        try (InputStream fileStream = new FileInputStream(translationFile)) {
-                            Long storageId = client.uploadStorage(translationFile.getName(), fileStream);
-                            request.setStorageId(storageId);
-                        } catch (Exception e) {
-                            containsErrors.set(true);
-                            throw new RuntimeException(String.format(
-                                RESOURCE_BUNDLE.getString("error.upload_translation_to_storage"),
-                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
-                            ), e);
-                        }
+                        request.setStorageId(uploadToStorage(pb, client, containsErrors, translationFile));
                         try {
                             for (Language lang : langs) {
                                 try {
@@ -211,30 +181,32 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                 Map<java.io.File, Pair<List<Language>, UploadTranslationsStringsRequest>> preparedRequests = new HashMap<>();
                 Branch branch = project.findBranchByName(branchName)
                     .orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project")));
-                for (Language language : languages) {
-                    UploadTranslationsStringsRequest request = new UploadTranslationsStringsRequest();
-                    request.setBranchId(branch.getId());
-                    request.setTranslateHidden(translateHidden);
-                    request.setAutoApproveImported(autoApproveImported);
-                    request.setImportEqSuggestions(importEqSuggestions);
-                    preparedRequests.put(new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + file.getTranslation()), Pair.of(Collections.singletonList(language), request));
-                }
+                fileSourcesWithoutIgnores.forEach(source -> {
+                    String fileSource = StringUtils.removeStart(source, pb.getBasePath());
+                    String translation = TranslationsUtils.replaceDoubleAsterisk(file.getSource(), file.getTranslation(), fileSource);
+                    translation = placeholderUtil.replaceFileDependentPlaceholders(translation, new java.io.File(source));
+                    if (file.getScheme() != null && !PlaceholderUtil.containsLangPlaceholders(translation)) {
+                        java.io.File transFile = getTranslationFile(out, pb, translation);
+                        if (Objects.isNull(transFile)) return;
+                        UploadTranslationsStringsRequest request = RequestBuilder.uploadTranslationsStrings(branch.getId(), importEqSuggestions, autoApproveImported, translateHidden);
+                        preparedRequests.put(transFile, Pair.of(languages, request));
+                    } else {
+                        for (Language language : languages) {
+                            java.io.File transFile = getTranslationFileWithPlaceholders(out, pb, placeholderUtil, serverLanguageMapping, file, translation, language);
+                            if (Objects.isNull(transFile)) continue;
+                            UploadTranslationsStringsRequest request = RequestBuilder.uploadTranslationsStrings(branch.getId(), importEqSuggestions, autoApproveImported, translateHidden);
+                            preparedRequests.put(transFile, Pair.of(Collections.singletonList(language), request));
+                        }
+                    }
+
+                });
                 tasks = preparedRequests.entrySet()
                     .stream()
                     .map(entry -> (Runnable) () -> {
                         java.io.File translationFile = entry.getKey();
                         List<Language> langs = entry.getValue().getLeft();
                         UploadTranslationsStringsRequest request = entry.getValue().getRight();
-                        try (InputStream fileStream = new FileInputStream(translationFile)) {
-                            Long storageId = client.uploadStorage(translationFile.getName(), fileStream);
-                            request.setStorageId(storageId);
-                        } catch (Exception e) {
-                            containsErrors.set(true);
-                            throw new RuntimeException(String.format(
-                                RESOURCE_BUNDLE.getString("error.upload_translation_to_storage"),
-                                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
-                            ), e);
-                        }
+                        request.setStorageId(uploadToStorage(pb, client, containsErrors, translationFile));
                         try {
                             for (Language lang : langs) {
                                 client.uploadTranslationStringsBased(lang.getId(), request);
@@ -263,4 +235,41 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
             }
         }
     }
+
+    private Long uploadToStorage(PropertiesWithFiles pb, ProjectClient client, AtomicBoolean containsErrors, java.io.File translationFile) {
+        try (InputStream fileStream = new FileInputStream(translationFile)) {
+            Long storageId = client.uploadStorage(translationFile.getName(), fileStream);
+            return storageId;
+        } catch (Exception e) {
+            containsErrors.set(true);
+            throw new RuntimeException(String.format(
+                RESOURCE_BUNDLE.getString("error.upload_translation_to_storage"),
+                StringUtils.removeStart(translationFile.getAbsolutePath(), pb.getBasePath())
+            ), e);
+        }
+    }
+
+    private java.io.File getTranslationFileWithPlaceholders(Outputter out, PropertiesWithFiles pb, PlaceholderUtil placeholderUtil, LanguageMapping serverLanguageMapping, FileBean file, String translation, Language language) {
+        LanguageMapping localLanguageMapping =
+            LanguageMapping.fromConfigFileLanguageMapping(file.getLanguagesMapping());
+        LanguageMapping languageMapping =
+            LanguageMapping.populate(localLanguageMapping, serverLanguageMapping);
+
+        String transFileName = placeholderUtil.replaceLanguageDependentPlaceholders(translation, languageMapping, language);
+        transFileName = PropertiesBeanUtils.useTranslationReplace(transFileName, file.getTranslationReplace());
+        return getTranslationFile(out, pb, transFileName);
+    }
+
+    private java.io.File getTranslationFile(Outputter out, PropertiesWithFiles pb, String translation) {
+        java.io.File transFile = new java.io.File(pb.getBasePath() + Utils.PATH_SEPARATOR + translation);
+        if (!transFile.exists()) {
+            if (!plainView) {
+                out.println(SKIPPED.withIcon(String.format(
+                    RESOURCE_BUNDLE.getString("error.translation_not_exists"),
+                    StringUtils.removeStart(transFile.getAbsolutePath(), pb.getBasePath()))));
+            }
+            return null;
+        }
+        return transFile;
+    }
 }
diff --git a/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java b/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java
index e3eb1a9bf..bc323cdc2 100644
--- a/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java
+++ b/src/main/java/com/crowdin/cli/commands/functionality/RequestBuilder.java
@@ -110,6 +110,15 @@ public static UploadTranslationsRequest uploadTranslations(Long fileId, boolean
         return request;
     }
 
+    public static UploadTranslationsStringsRequest uploadTranslationsStrings(Long branchId, boolean importEqSuggestions, boolean autoApproveImported, boolean translateHidden) {
+        UploadTranslationsStringsRequest request = new UploadTranslationsStringsRequest();
+        request.setBranchId(branchId);
+        request.setImportEqSuggestions(importEqSuggestions);
+        request.setAutoApproveImported(autoApproveImported);
+        request.setTranslateHidden(translateHidden);
+        return request;
+    }
+
     public static PatchRequest patch(Object value, PatchOperation op, String path) {
         PatchRequest request = new PatchRequest();
         request.setValue(value);
diff --git a/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java b/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java
index 0b6f0faa9..9c25c170d 100644
--- a/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java
+++ b/src/main/java/com/crowdin/cli/commands/picocli/StringAddSubcommand.java
@@ -35,7 +35,7 @@ class StringAddSubcommand extends ActCommandProject {
     @CommandLine.Option(names = {"--label"}, descriptionKey = "params.label", paramLabel = "...", order = -2)
     protected List<String> labelNames;
 
-    @CommandLine.Option(names = {"--branch"}, descriptionKey = "branch", paramLabel = "...", order = -2)
+    @CommandLine.Option(names = {"-b", "--branch"}, descriptionKey = "branch", paramLabel = "...", order = -2)
     protected String branch;
 
     @CommandLine.Option(names = {"--hidden"}, order = -2)
diff --git a/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java b/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java
index 3cd1cb4b8..8b3ba4a29 100644
--- a/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java
+++ b/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java
@@ -65,7 +65,7 @@ public void populateWithDefaultValues(PropertiesWithFiles props) {
         public PropertiesBuilder.Messages checkProperties(PropertiesWithFiles props, CheckType checkType) {
             PropertiesBuilder.Messages messages = new PropertiesBuilder.Messages();
             if (props.getFiles() == null || props.getFiles().isEmpty()) {
-                messages.addError(RESOURCE_BUNDLE.getString("error.config.empty_or_missed_section_files"));
+                messages.addWarning(RESOURCE_BUNDLE.getString("message.warning.empty_or_missed_section_files"));
             } else {
                 for (FileBean fileBean : props.getFiles()) {
                     messages.addAllErrors(FileBean.CONFIGURATOR.checkProperties(fileBean));
diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties
index d4f063754..84699dccf 100755
--- a/src/main/resources/messages/messages.properties
+++ b/src/main/resources/messages/messages.properties
@@ -541,6 +541,7 @@ error.comment_should_not_have_issue_type=Comment should not have the --issue-typ
 
 error.distribution_is_not_added=Distribution was not added
 error.distribution_is_not_released=Distribution was not released
+error.distribution_failed=Distribution release failed
 error.distribution.empty_file=The '--file' value can not be empty for the 'default' export mode
 error.distribution.empty_bundle_ids=The '--bundle-id' value can not be empty for the 'bundle' export mode
 error.distribution.incorrect_file_command_usage=The '--file' is used only for the 'default' export mode
@@ -747,6 +748,7 @@ message.no_manager_access=You need to have @|yellow manager access|@ in the proj
 message.no_manager_access_in_upload_sources=You need to have @|yellow manager access|@ in the project to apply 'excluded-languages' or/and 'delete-obsolete' options
 message.no_manager_access_in_upload_sources_dryrun=You need to have @|yellow manager access|@ in the project to apply 'delete-obsolete' options
 
+message.warning.empty_or_missed_section_files=Required section 'files' is missing (or empty) in the configuration file. If you are working with a string-based project, you can ignore this message
 message.warning.not_yml=File @|bold '%s'|@ is not a YAML or YML file
 message.warning.browser_not_found=\nError opening a web browser. Please open the following link manually:\n@|bold %s|@
 message.warning.file_not_uploaded_cause_of_language=Translation file @|yellow,bold '%s'|@ @|yellow hasn't been uploaded|@ since @|bold %s|@ is not enabled as a target language for the source file in your Crowdin project
diff --git a/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java b/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java
index 96aadd992..27d16c2f8 100644
--- a/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java
+++ b/src/test/java/com/crowdin/cli/commands/actions/UploadTranslationsActionTest.java
@@ -213,9 +213,9 @@ public void testUploadWithDest() throws ResponseException {
     @Test
     public void testUploadOneOfTwoTranslation_StringBasedProject() throws ResponseException {
         project.addFile(Utils.normalizePath("first.po"), "Hello, World!");
-        project.addFile(Utils.normalizePath("translation.po"), "Hello, World!");
+        project.addFile(Utils.normalizePath("first.po-CR-uk-UA"), "Hello, World!");
         NewPropertiesWithFilesUtilBuilder pbBuilder = NewPropertiesWithFilesUtilBuilder
-            .minimalBuiltPropertiesBean("*", "translation.po")
+            .minimalBuiltPropertiesBean("*", Utils.PATH_SEPARATOR + "%original_file_name%-CR-%locale%", Arrays.asList("*-CR-*"))
             .setBasePath(project.getBasePath());
         PropertiesWithFiles pb = pbBuilder.build();
         ProjectClient client = mock(ProjectClient.class);
@@ -223,14 +223,14 @@ public void testUploadOneOfTwoTranslation_StringBasedProject() throws ResponseEx
         build.setType(Type.STRINGS_BASED);
         when(client.downloadFullProject("main"))
             .thenReturn(build);
-        when(client.uploadStorage(eq("translation.po"), any()))
+        when(client.uploadStorage(eq("first.po-CR-uk-UA"), any()))
             .thenReturn(1L);
 
         NewAction<PropertiesWithFiles, ProjectClient> action = new UploadTranslationsAction(false, null, "main", false, false, false, false, false);
         assertDoesNotThrow(() -> action.act(Outputter.getDefault(), pb, client));
 
         verify(client).downloadFullProject("main");
-        verify(client).uploadStorage(eq("translation.po"), any());
+        verify(client).uploadStorage(eq("first.po-CR-uk-UA"), any());
         UploadTranslationsStringsRequest uploadTranslationRequest = new UploadTranslationsStringsRequest() {{
             setStorageId(1L);
             setBranchId(2L);

From 4fc77432eb1e5edc50337c21651af8bf84c921bd Mon Sep 17 00:00:00 2001
From: Kateryna Oblakevych <malutina.catya14@gmail.com>
Date: Wed, 31 Jan 2024 12:22:31 +0200
Subject: [PATCH 3/5] manager access check in file upload

---
 .../cli/commands/actions/FileUploadAction.java        | 11 +++++++++++
 .../commands/actions/FileUploadTranslationAction.java |  9 +++++++++
 2 files changed, 20 insertions(+)

diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java
index fb9a2f9c3..13f2517d0 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java
@@ -4,6 +4,7 @@
 import com.crowdin.cli.commands.NewAction;
 import com.crowdin.cli.commands.Outputter;
 import com.crowdin.cli.commands.functionality.*;
+import com.crowdin.cli.properties.FileBean;
 import com.crowdin.cli.properties.ProjectProperties;
 import com.crowdin.cli.utils.PlaceholderUtil;
 import com.crowdin.cli.utils.Utils;
@@ -47,6 +48,16 @@ class FileUploadAction implements NewAction<ProjectProperties, ProjectClient> {
     public void act(Outputter out, ProjectProperties properties, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.plainView, this.plainView, client::downloadFullProject);
+
+        if (!project.isManagerAccess()) {
+            if (!plainView) {
+                out.println(WARNING.withIcon(RESOURCE_BUNDLE.getString("message.no_manager_access")));
+                return;
+            } else {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_manager_access"));
+            }
+        }
+
         PlaceholderUtil placeholderUtil = new PlaceholderUtil(project.getSupportedLanguages(), project.getProjectLanguages(false), properties.getBasePath());
 
         Optional<List<Long>> attachLabelIds = Optional.empty();
diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java
index 5dd5b7e43..869151f0e 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java
@@ -40,6 +40,15 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.plainView, this.plainView, () -> client.downloadFullProject(branchName));
 
+        if (!project.isManagerAccess()) {
+            if (!plainView) {
+                out.println(WARNING.withIcon(RESOURCE_BUNDLE.getString("message.no_manager_access")));
+                return;
+            } else {
+                throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_manager_access"));
+            }
+        }
+
         if (!project.findLanguageById(languageId, true).isPresent()) {
             throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.language_not_exist"), languageId));
         }

From 3fa8cd4cc83a92be1e6940e95cb19fefc1e28ee8 Mon Sep 17 00:00:00 2001
From: Kateryna Oblakevych <malutina.catya14@gmail.com>
Date: Wed, 31 Jan 2024 15:58:48 +0200
Subject: [PATCH 4/5] return error to files section validation

---
 .../java/com/crowdin/cli/properties/PropertiesWithFiles.java    | 2 +-
 src/main/resources/messages/messages.properties                 | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java b/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java
index 8b3ba4a29..3cd1cb4b8 100644
--- a/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java
+++ b/src/main/java/com/crowdin/cli/properties/PropertiesWithFiles.java
@@ -65,7 +65,7 @@ public void populateWithDefaultValues(PropertiesWithFiles props) {
         public PropertiesBuilder.Messages checkProperties(PropertiesWithFiles props, CheckType checkType) {
             PropertiesBuilder.Messages messages = new PropertiesBuilder.Messages();
             if (props.getFiles() == null || props.getFiles().isEmpty()) {
-                messages.addWarning(RESOURCE_BUNDLE.getString("message.warning.empty_or_missed_section_files"));
+                messages.addError(RESOURCE_BUNDLE.getString("error.config.empty_or_missed_section_files"));
             } else {
                 for (FileBean fileBean : props.getFiles()) {
                     messages.addAllErrors(FileBean.CONFIGURATOR.checkProperties(fileBean));
diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties
index 84699dccf..2638ca3f4 100755
--- a/src/main/resources/messages/messages.properties
+++ b/src/main/resources/messages/messages.properties
@@ -748,7 +748,6 @@ message.no_manager_access=You need to have @|yellow manager access|@ in the proj
 message.no_manager_access_in_upload_sources=You need to have @|yellow manager access|@ in the project to apply 'excluded-languages' or/and 'delete-obsolete' options
 message.no_manager_access_in_upload_sources_dryrun=You need to have @|yellow manager access|@ in the project to apply 'delete-obsolete' options
 
-message.warning.empty_or_missed_section_files=Required section 'files' is missing (or empty) in the configuration file. If you are working with a string-based project, you can ignore this message
 message.warning.not_yml=File @|bold '%s'|@ is not a YAML or YML file
 message.warning.browser_not_found=\nError opening a web browser. Please open the following link manually:\n@|bold %s|@
 message.warning.file_not_uploaded_cause_of_language=Translation file @|yellow,bold '%s'|@ @|yellow hasn't been uploaded|@ since @|bold %s|@ is not enabled as a target language for the source file in your Crowdin project

From 1f017ad1a21f9dceb605185174a4090b53b725de Mon Sep 17 00:00:00 2001
From: Kateryna Oblakevych <malutina.catya14@gmail.com>
Date: Thu, 1 Feb 2024 18:09:19 +0200
Subject: [PATCH 5/5] fix project type check

---
 .../com/crowdin/cli/client/CrowdinProjectClient.java |  3 ++-
 .../cli/commands/actions/DistributionAddAction.java  | 10 ++++++----
 .../commands/actions/DistributionReleaseAction.java  |  6 ++++--
 .../cli/commands/actions/FileDeleteAction.java       |  3 ++-
 .../cli/commands/actions/FileDownloadAction.java     |  3 ++-
 .../actions/FileDownloadTranslationAction.java       |  3 ++-
 .../cli/commands/actions/FileUploadAction.java       |  9 +++++----
 .../actions/FileUploadTranslationAction.java         |  5 +++--
 .../cli/commands/actions/PreTranslateAction.java     |  5 +++--
 .../cli/commands/actions/StringAddAction.java        |  5 +++--
 .../cli/commands/actions/StringListAction.java       |  8 ++++----
 .../cli/commands/actions/UploadSourcesAction.java    | 12 ++++++------
 .../commands/actions/UploadTranslationsAction.java   |  5 +++--
 13 files changed, 45 insertions(+), 32 deletions(-)

diff --git a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
index babadd4c3..3d6e7a809 100644
--- a/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
+++ b/src/main/java/com/crowdin/cli/client/CrowdinProjectClient.java
@@ -64,7 +64,8 @@ private void populateProjectWithStructure(CrowdinProjectFull project, String bra
                 .ifPresent(project::setBranch);
         Long branchId = Optional.ofNullable(project.getBranch()).map(Branch::getId).orElse(null);
 
-        if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
+        if (isStringsBasedProject) {
             return;
         }
         project.setFiles(executeRequestFullList((limit, offset) -> this.client.getSourceFilesApi()
diff --git a/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java b/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java
index ded47b1a5..d747e1426 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/DistributionAddAction.java
@@ -51,9 +51,11 @@ public void act(Outputter out, ProjectProperties pb, ClientDistribution client)
                 this.plainView,
                 () -> this.projectClient.downloadFullProject(this.branch)
         );
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
+
         List<Long> fileIds = null;
         if (files != null) {
-            if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            if (isStringsBasedProject) {
                 throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_file_string_project"));
             }
             Map<String, Long> projectBranches = project.getBranches().values().stream()
@@ -81,12 +83,12 @@ public void act(Outputter out, ProjectProperties pb, ClientDistribution client)
                     .filter(file -> files.contains(file.getPath()))
                     .map(FileInfo::getId)
                     .collect(Collectors.toList());
-        } else if (exportMode == DEFAULT && Objects.equals(project.getType(), Type.FILES_BASED)) {
+        } else if (exportMode == DEFAULT && !isStringsBasedProject) {
             throw new RuntimeException(RESOURCE_BUNDLE.getString("error.distribution.empty_file"));
         }
 
         Distribution distribution = null;
-        if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+        if (!isStringsBasedProject) {
             AddDistributionRequest addDistributionRequest = RequestBuilder.addDistribution(name, exportMode, fileIds, bundleIds);
             Optional.ofNullable(name).ifPresent(addDistributionRequest::setName);
             Optional.ofNullable(exportMode).ifPresent(addDistributionRequest::setExportMode);
@@ -98,7 +100,7 @@ public void act(Outputter out, ProjectProperties pb, ClientDistribution client)
             } catch (Exception e) {
                 throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.distribution_is_not_added"), addDistributionRequest), e);
             }
-        } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        } else if (isStringsBasedProject) {
             AddDistributionStringsBasedRequest addDistributionRequest = new AddDistributionStringsBasedRequest();
             addDistributionRequest.setName(name);
             if (Objects.isNull(bundleIds)) {
diff --git a/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java b/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
index 4fd7a6ad3..5b6ae340f 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/DistributionReleaseAction.java
@@ -35,9 +35,11 @@ public void act(Outputter out, ProjectProperties pb, ClientDistribution client)
             this.plainView,
             () -> this.projectClient.downloadProjectInfo()
         );
-        if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
+
+        if (!isStringsBasedProject) {
             this.releaseDistributionFilesBased(out, client);
-        } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        } else {
             this.releaseDistributionStringsBased(out, client);
         }
         out.println(OK.withIcon(String.format(RESOURCE_BUNDLE.getString("message.distribution.released"), hash)));
diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileDeleteAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileDeleteAction.java
index 83babd4de..f37f7713d 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileDeleteAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileDeleteAction.java
@@ -30,7 +30,8 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
         CrowdinProjectFull project = ConsoleSpinner
             .execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
                 true, true, client::downloadFullProject);
-        if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
+        if (isStringsBasedProject) {
             out.println(SKIPPED.withIcon(RESOURCE_BUNDLE.getString("message.no_file_string_project")));
             return;
         }
diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileDownloadAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileDownloadAction.java
index 86b2a6c18..8b9a021e2 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileDownloadAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileDownloadAction.java
@@ -35,7 +35,8 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
         CrowdinProjectFull project = ConsoleSpinner
             .execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
                 false, false, () -> client.downloadFullProject(branch));
-        if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
+        if (isStringsBasedProject) {
             out.println(WARNING.withIcon(RESOURCE_BUNDLE.getString("message.no_file_string_project")));
             return;
         }
diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileDownloadTranslationAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileDownloadTranslationAction.java
index dc2e3ff5a..5d9937b94 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileDownloadTranslationAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileDownloadTranslationAction.java
@@ -39,7 +39,8 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
         CrowdinProjectFull project = ConsoleSpinner
             .execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
                 false, false, client::downloadFullProject);
-        if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
+        if (isStringsBasedProject) {
             out.println(WARNING.withIcon(RESOURCE_BUNDLE.getString("message.no_file_string_project")));
             return;
         }
diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java
index 13f2517d0..e9931e479 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileUploadAction.java
@@ -48,6 +48,7 @@ class FileUploadAction implements NewAction<ProjectProperties, ProjectClient> {
     public void act(Outputter out, ProjectProperties properties, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.plainView, this.plainView, client::downloadFullProject);
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         if (!project.isManagerAccess()) {
             if (!plainView) {
@@ -67,7 +68,7 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
 
         String fileFullPath = file.getPath();
         String fileDestName = file.getName();
-        if (Objects.equals(Type.FILES_BASED, project.getType())) {
+        if (!isStringsBasedProject) {
             String commonPath = SourcesUtils.getCommonPath(Collections.singletonList(this.file.getAbsolutePath()), properties.getBasePath());
             final String filePath = (nonNull(dest))
                 ? PropertiesBeanUtils.prepareDest(dest, StringUtils.removeStart(file.getAbsolutePath(), properties.getBasePath()), placeholderUtil)
@@ -121,7 +122,7 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
         Optional<Branch> branch = Optional.empty();
         if (StringUtils.isNotEmpty(branchName)) {
             branch = Optional.ofNullable(BranchUtils.getOrCreateBranch(out, branchName, client, project, plainView));
-        } else if (Objects.equals(Type.STRINGS_BASED, project.getType())) {
+        } else if (isStringsBasedProject) {
             throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"));
         }
 
@@ -132,7 +133,7 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
             excludedLanguageNames = Optional.of(filterExcludedLanguages(excludedLanguages, project));
         }
 
-        if (Objects.equals(Type.FILES_BASED, project.getType())) {
+        if (!isStringsBasedProject) {
             AddFileRequest request = new AddFileRequest();
             request.setName(fileDestName);
             request.setStorageId(storageId);
@@ -152,7 +153,7 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
             }
 
         }
-        if (Objects.equals(Type.STRINGS_BASED, project.getType())) {
+        if (isStringsBasedProject) {
             UploadStringsRequest request = new UploadStringsRequest();
             request.setBranchId(branch.orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"))).getId());
             request.setCleanupMode(cleanupMode);
diff --git a/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java b/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java
index 869151f0e..1f56ec0ac 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/FileUploadTranslationAction.java
@@ -39,6 +39,7 @@ public class FileUploadTranslationAction implements NewAction<ProjectProperties,
     public void act(Outputter out, ProjectProperties properties, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.plainView, this.plainView, () -> client.downloadFullProject(branchName));
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         if (!project.isManagerAccess()) {
             if (!plainView) {
@@ -53,7 +54,7 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
             throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.language_not_exist"), languageId));
         }
 
-        if (Objects.equals(Type.FILES_BASED, project.getType())) {
+        if (!isStringsBasedProject) {
             if (Objects.isNull(dest))
                 throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.file.dest_required"), languageId));
             String sourcePath = Utils.toUnixPath(Utils.sepAtStart(dest));
@@ -72,7 +73,7 @@ public void act(Outputter out, ProjectProperties properties, ProjectClient clien
                 throw new RuntimeException(String.format(
                     RESOURCE_BUNDLE.getString("error.upload_translation"), file.getPath()), e);
             }
-        } else if (Objects.equals(Type.STRINGS_BASED, project.getType())) {
+        } else {
             UploadTranslationsStringsRequest request = new UploadTranslationsStringsRequest();
             Branch branch = project.findBranchByName(branchName)
                 .orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project")));
diff --git a/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java b/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
index f82c161f9..2e99ff9df 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/PreTranslateAction.java
@@ -65,11 +65,12 @@ public PreTranslateAction(
     public void act(Outputter out, PropertiesWithFiles properties, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.noProgress, this.plainView, () -> client.downloadFullProject(this.branchName));
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         List<String> languages = this.prepareLanguageIds(project);
         List<Long> labelIds = this.prepareLabelIds(out, client);
 
-        if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+        if (!isStringsBasedProject) {
             List<Long> fileIds = this.prepareFileIds(out, properties, project);
             if (fileIds == null || fileIds.isEmpty()) {
                 throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.no_files_found_for_pre_translate")));
@@ -78,7 +79,7 @@ public void act(Outputter out, PropertiesWithFiles properties, ProjectClient cli
                 languages, fileIds, method, engineId, autoApproveOption,
                 duplicateTranslations, translateUntranslatedOnly, translateWithPerfectMatchOnly, labelIds);
             this.applyPreTranslation(out, client, request);
-        } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+        } else {
             Branch branch = project.findBranchByName(branchName)
                 .orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project")));
             ApplyPreTranslationStringsBasedRequest request = RequestBuilder.applyPreTranslationStringsBased(
diff --git a/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java b/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java
index 1cc425b81..11c890fbe 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/StringAddAction.java
@@ -55,11 +55,12 @@ public StringAddAction(
     public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.noProgress, false, client::downloadFullProject);
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         List<Long> labelIds = (labelNames != null && !labelNames.isEmpty()) ? this.prepareLabelIds(client) : null;
 
         if (files == null || files.isEmpty()) {
-            if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            if (isStringsBasedProject) {
                 Branch branch = BranchUtils.getOrCreateBranch(out, branchName, client, project, false);
                 if (Objects.isNull(branch)) {
                     throw new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project"));
@@ -72,7 +73,7 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
             }
             out.println(OK.withIcon(RESOURCE_BUNDLE.getString("message.source_string_uploaded")));
         } else {
-            if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            if (isStringsBasedProject) {
                 throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_file_string_project"));
             }
             Map<String, FileInfo> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
diff --git a/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java b/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java
index 3601da382..b78b66ba4 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/StringListAction.java
@@ -46,6 +46,7 @@ public StringListAction(boolean noProgress, boolean isVerbose, String file, Stri
     public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.noProgress, false, () -> client.downloadFullProject(this.branchName));
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         Long branchId = Optional.ofNullable(project.getBranch())
             .map(Branch::getId)
@@ -54,10 +55,9 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         Map<Long, String> labels = client.listLabels().stream()
             .collect(Collectors.toMap(Label::getId, Label::getTitle));
 
-        boolean isFileBasedProject = Objects.equals(project.getType(), Type.FILES_BASED);
         Map<String, FileInfo> paths = null;
         Map<Long, String> reversePaths = null;
-        if (isFileBasedProject) {
+        if (!isStringsBasedProject) {
             paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
             reversePaths = paths.entrySet()
                 .stream()
@@ -72,7 +72,7 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
         if (StringUtils.isEmpty(file)) {
             sourceStrings = client.listSourceString(null, branchId, null, encodedFilter, encodedCroql);
         } else {
-            if (!isFileBasedProject) {
+            if (isStringsBasedProject) {
                 throw new RuntimeException(RESOURCE_BUNDLE.getString("message.no_file_string_project"));
             }
             if (paths.containsKey(file)) {
@@ -97,7 +97,7 @@ public void act(Outputter out, ProjectProperties pb, ProjectClient client) {
                     out.println(String.format(
                         RESOURCE_BUNDLE.getString("message.source_string_list_context"), ss.getContext().trim().replaceAll("\n", "\n\t\t")));
                 }
-                if (isFileBasedProject && (ss.getFileId() != null)) {
+                if (!isStringsBasedProject && (ss.getFileId() != null)) {
                     out.println(String.format(RESOURCE_BUNDLE.getString("message.source_string_list_file"), finalReversePaths.get(ss.getFileId())));
                 }
                 if (ss.getMaxLength() != null && ss.getMaxLength() != 0) {
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 edbd0a4a5..4f741a4ff 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/UploadSourcesAction.java
@@ -57,6 +57,7 @@ public UploadSourcesAction(String branchName, boolean deleteObsolete, boolean no
     public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.noProgress, this.plainView, client::downloadFullProject);
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         boolean containsExcludedLanguages = pb.getFiles().stream()
             .map(FileBean::getExcludedTargetLanguages).filter(Objects::nonNull).anyMatch(l -> !l.isEmpty());
@@ -74,12 +75,11 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
         Branch branch = (branchName != null) ? BranchUtils.getOrCreateBranch(out, branchName, client, project, plainView) : null;
         Long branchId = (branch != null) ? branch.getId() : null;
 
-        boolean isFilesBasedProject = Objects.equals(Type.FILES_BASED, project.getType());
         Map<String, Long> directoryPaths = null;
         Map<String, FileInfo> paths = null;
         DeleteObsoleteProjectFilesSubAction deleteObsoleteProjectFilesSubAction = new DeleteObsoleteProjectFilesSubAction(out, client);
 
-        if (isFilesBasedProject) {
+        if (!isStringsBasedProject) {
             directoryPaths = ProjectFilesUtils.buildDirectoryPaths(project.getDirectories(), project.getBranches())
                 .entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
             paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFileInfos());
@@ -175,8 +175,8 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                             uploadedSources.add(fileFullPath);
                         }
 
-                        FileInfo projectFile = isFilesBasedProject ? finalPaths.get(fileFullPath) : null;
-                        if (isFilesBasedProject && autoUpdate && projectFile != null) {
+                        FileInfo projectFile = !isStringsBasedProject ? finalPaths.get(fileFullPath) : null;
+                        if (!isStringsBasedProject && autoUpdate && projectFile != null) {
                             final UpdateFileRequest request = new UpdateFileRequest();
                             request.setExportOptions(buildExportOptions(sourceFile, file, pb.getBasePath()));
                             request.setImportOptions(buildImportOptions(sourceFile, file, srxStorageId));
@@ -225,7 +225,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                                     throw new RuntimeException(String.format(RESOURCE_BUNDLE.getString("error.uploading_file"), fileFullPath), e);
                                 }
                             };
-                        } else if (projectFile == null && isFilesBasedProject) {
+                        } else if (projectFile == null && !isStringsBasedProject) {
                             final AddFileRequest request = new AddFileRequest();
                             request.setName(fileName);
                             request.setExportOptions(buildExportOptions(sourceFile, file, pb.getBasePath()));
@@ -282,7 +282,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                                     out.println(fileFullPath);
                                 }
                             };
-                        } else if (Objects.equals(Type.STRINGS_BASED, project.getType())) {
+                        } else if (isStringsBasedProject) {
                             final UploadStringsRequest request = new UploadStringsRequest();
                             request.setImportOptions(buildImportOptionsStringsBased(sourceFile, file, srxStorageId));
                             if (file.getType() != null) {
diff --git a/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java b/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
index 5c86516e1..b49a35e60 100644
--- a/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
+++ b/src/main/java/com/crowdin/cli/commands/actions/UploadTranslationsAction.java
@@ -67,6 +67,7 @@ public UploadTranslationsAction(
     public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
         CrowdinProjectFull project = ConsoleSpinner.execute(out, "message.spinner.fetching_project_info", "error.collect_project_info",
             this.noProgress, this.plainView, () -> client.downloadFullProject(this.branchName));
+        boolean isStringsBasedProject = Objects.equals(project.getType(), Type.STRINGS_BASED);
 
         if (!project.isManagerAccess()) {
             if (!plainView) {
@@ -105,7 +106,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
             }
             List<Runnable> tasks = null;
             AtomicBoolean containsErrors = new AtomicBoolean(false);
-            if (Objects.equals(project.getType(), Type.FILES_BASED)) {
+            if (!isStringsBasedProject) {
                 Map<String, File> paths = ProjectFilesUtils.buildFilePaths(project.getDirectories(), project.getBranches(), project.getFiles());
                 Map<java.io.File, Pair<List<Language>, UploadTranslationsRequest>> preparedRequests = new HashMap<>();
                 String branchPath = (StringUtils.isNotEmpty(this.branchName) ? branchName + Utils.PATH_SEPARATOR : "");
@@ -177,7 +178,7 @@ public void act(Outputter out, PropertiesWithFiles pb, ProjectClient client) {
                         }
                     })
                     .collect(Collectors.toList());
-            } else if (Objects.equals(project.getType(), Type.STRINGS_BASED)) {
+            } else {
                 Map<java.io.File, Pair<List<Language>, UploadTranslationsStringsRequest>> preparedRequests = new HashMap<>();
                 Branch branch = project.findBranchByName(branchName)
                     .orElseThrow(() -> new RuntimeException(RESOURCE_BUNDLE.getString("error.branch_required_string_project")));