From 7c28a73e3eff974cf41ffbaf5111144330520966 Mon Sep 17 00:00:00 2001 From: RiabushenkoA Date: Tue, 7 Jan 2025 19:35:22 +0200 Subject: [PATCH] App release --- .gitignore | 9 + App.Systran.sln | 31 + App.sln | 15 - Apps.App/Actions/Actions.cs | 11 - Apps.App/Api/AppClient.cs | 11 - Apps.App/Apps.App.csproj | 22 - Apps.App/Connections/ConnectionValidator.cs | 17 - Apps.App/Constants/CredsNames.cs | 6 - Apps.Systran/Actions/Base/BaseActions.cs | 20 + Apps.Systran/Actions/CorpusActions.cs | 82 + Apps.Systran/Actions/DictionaryActions.cs | 190 +++ Apps.Systran/Actions/TranslateFileActions.cs | 164 ++ Apps.Systran/Actions/TranslateTextActions.cs | 56 + Apps.Systran/Api/SystranClient.cs | 58 + Apps.Systran/Api/SystranRequest.cs | 11 + {Apps.App => Apps.Systran}/Application.cs | 2 +- Apps.Systran/Apps.Systran.csproj | 23 + .../Connections/ConnectionDefinition.cs | 5 +- .../Connections/ConnectionValidator.cs | 32 + Apps.Systran/Constants/CredsNames.cs | 7 + .../DataSourceHandlers/CorporaDataHandler.cs | 28 + .../DictionaryDataHandler.cs | 27 + .../CorpusFormatDataHandler .cs | 23 + .../DictionaryTypeDataHandler .cs | 23 + .../LanguageCodeDataHandler.cs | 180 +++ .../EnumDataHandlers/SourcePosDataHandler .cs | 31 + .../ProfilesDataHandler .cs | 32 + Apps.Systran/Dto/SystranError.cs | 8 + Apps.Systran/Dto/SystranStatusResponse.cs | 7 + Apps.Systran/Dto/TranslationStatusDto.cs | 8 + .../Invocables/SystranInvocable.cs | 6 +- .../Request/CreateDictionaryParameters.cs | 26 + .../Models/Request/ExportCorpusParameters.cs | 13 + .../Models/Request/ExportDictionaryRequest.cs | 13 + .../Models/Request/ImportCorpusParameters.cs | 18 + .../Models/Request/TranslateFileRequest.cs | 17 + .../Models/Request/TranslateTextRequest.cs | 28 + .../Models/Request/UpdateDictionaryRequest.cs | 17 + .../Models/Response/CorporaListResponse.cs | 16 + .../Response/CreateDictionaryResponse.cs | 33 + .../Response/DictionaryDataHandlerResponse.cs | 19 + Apps.Systran/Models/Response/ErrorResponse.cs | 9 + .../Models/Response/FileReferenceResponse.cs | 11 + .../Models/Response/ImportCorpusResponse.cs | 17 + .../Models/Response/ImportResponse.cs | 12 + .../Models/Response/ListEntriesResponse.cs | 19 + .../Models/Response/ProfilesListResponse.cs | 21 + .../Response/TranslateFileAsyncResponse.cs | 10 + .../Models/Response/TranslateTextResponse.cs | 44 + .../Response/UpdateDictionaryResponse.cs | 13 + .../Models/TranslateLanguagesOptions.cs | 17 + .../Polling/Models/TranslateFileMemory.cs | 9 + .../Models/TranslationResultResponse.cs | 12 + .../Models/TranslationStatusResponse.cs | 29 + .../Polling/TranslateFilePollingList.cs | 90 ++ Apps.Systran/README.md | 60 + Apps.Systran/image/icon.png | Bin 0 -> 4281 bytes README.md | 49 +- Tests.Systran/Base/FileManager.cs | 64 + Tests.Systran/Base/TestBase.cs | 34 + Tests.Systran/CorpusTests.cs | 56 + Tests.Systran/DataSources.cs | 68 + Tests.Systran/DictionaryTests.cs | 91 ++ Tests.Systran/GlobalUsings.cs | 1 + Tests.Systran/PollingTests.cs | 74 + Tests.Systran/TestFiles/Input/Translate.txt | 13 + Tests.Systran/TestFiles/Input/test.tbx | 1358 +++++++++++++++++ Tests.Systran/TestFiles/Input/test.tmx | 2 + Tests.Systran/Tests.Systran.csproj | 48 + Tests.Systran/TranslationTests.cs | 94 ++ Tests.Systran/Validator.cs | 29 + Tests.Systran/appsettings.json.example | 7 + 72 files changed, 3616 insertions(+), 90 deletions(-) create mode 100644 App.Systran.sln delete mode 100644 App.sln delete mode 100644 Apps.App/Actions/Actions.cs delete mode 100644 Apps.App/Api/AppClient.cs delete mode 100644 Apps.App/Apps.App.csproj delete mode 100644 Apps.App/Connections/ConnectionValidator.cs delete mode 100644 Apps.App/Constants/CredsNames.cs create mode 100644 Apps.Systran/Actions/Base/BaseActions.cs create mode 100644 Apps.Systran/Actions/CorpusActions.cs create mode 100644 Apps.Systran/Actions/DictionaryActions.cs create mode 100644 Apps.Systran/Actions/TranslateFileActions.cs create mode 100644 Apps.Systran/Actions/TranslateTextActions.cs create mode 100644 Apps.Systran/Api/SystranClient.cs create mode 100644 Apps.Systran/Api/SystranRequest.cs rename {Apps.App => Apps.Systran}/Application.cs (84%) create mode 100644 Apps.Systran/Apps.Systran.csproj rename {Apps.App => Apps.Systran}/Connections/ConnectionDefinition.cs (81%) create mode 100644 Apps.Systran/Connections/ConnectionValidator.cs create mode 100644 Apps.Systran/Constants/CredsNames.cs create mode 100644 Apps.Systran/DataSourceHandlers/CorporaDataHandler.cs create mode 100644 Apps.Systran/DataSourceHandlers/DictionaryDataHandler.cs create mode 100644 Apps.Systran/DataSourceHandlers/EnumDataHandlers/CorpusFormatDataHandler .cs create mode 100644 Apps.Systran/DataSourceHandlers/EnumDataHandlers/DictionaryTypeDataHandler .cs create mode 100644 Apps.Systran/DataSourceHandlers/EnumDataHandlers/LanguageCodeDataHandler.cs create mode 100644 Apps.Systran/DataSourceHandlers/EnumDataHandlers/SourcePosDataHandler .cs create mode 100644 Apps.Systran/DataSourceHandlers/ProfilesDataHandler .cs create mode 100644 Apps.Systran/Dto/SystranError.cs create mode 100644 Apps.Systran/Dto/SystranStatusResponse.cs create mode 100644 Apps.Systran/Dto/TranslationStatusDto.cs rename Apps.App/Invocables/AppInvocable.cs => Apps.Systran/Invocables/SystranInvocable.cs (67%) create mode 100644 Apps.Systran/Models/Request/CreateDictionaryParameters.cs create mode 100644 Apps.Systran/Models/Request/ExportCorpusParameters.cs create mode 100644 Apps.Systran/Models/Request/ExportDictionaryRequest.cs create mode 100644 Apps.Systran/Models/Request/ImportCorpusParameters.cs create mode 100644 Apps.Systran/Models/Request/TranslateFileRequest.cs create mode 100644 Apps.Systran/Models/Request/TranslateTextRequest.cs create mode 100644 Apps.Systran/Models/Request/UpdateDictionaryRequest.cs create mode 100644 Apps.Systran/Models/Response/CorporaListResponse.cs create mode 100644 Apps.Systran/Models/Response/CreateDictionaryResponse.cs create mode 100644 Apps.Systran/Models/Response/DictionaryDataHandlerResponse.cs create mode 100644 Apps.Systran/Models/Response/ErrorResponse.cs create mode 100644 Apps.Systran/Models/Response/FileReferenceResponse.cs create mode 100644 Apps.Systran/Models/Response/ImportCorpusResponse.cs create mode 100644 Apps.Systran/Models/Response/ImportResponse.cs create mode 100644 Apps.Systran/Models/Response/ListEntriesResponse.cs create mode 100644 Apps.Systran/Models/Response/ProfilesListResponse.cs create mode 100644 Apps.Systran/Models/Response/TranslateFileAsyncResponse.cs create mode 100644 Apps.Systran/Models/Response/TranslateTextResponse.cs create mode 100644 Apps.Systran/Models/Response/UpdateDictionaryResponse.cs create mode 100644 Apps.Systran/Models/TranslateLanguagesOptions.cs create mode 100644 Apps.Systran/Polling/Models/TranslateFileMemory.cs create mode 100644 Apps.Systran/Polling/Models/TranslationResultResponse.cs create mode 100644 Apps.Systran/Polling/Models/TranslationStatusResponse.cs create mode 100644 Apps.Systran/Polling/TranslateFilePollingList.cs create mode 100644 Apps.Systran/README.md create mode 100644 Apps.Systran/image/icon.png create mode 100644 Tests.Systran/Base/FileManager.cs create mode 100644 Tests.Systran/Base/TestBase.cs create mode 100644 Tests.Systran/CorpusTests.cs create mode 100644 Tests.Systran/DataSources.cs create mode 100644 Tests.Systran/DictionaryTests.cs create mode 100644 Tests.Systran/GlobalUsings.cs create mode 100644 Tests.Systran/PollingTests.cs create mode 100644 Tests.Systran/TestFiles/Input/Translate.txt create mode 100644 Tests.Systran/TestFiles/Input/test.tbx create mode 100644 Tests.Systran/TestFiles/Input/test.tmx create mode 100644 Tests.Systran/Tests.Systran.csproj create mode 100644 Tests.Systran/TranslationTests.cs create mode 100644 Tests.Systran/Validator.cs create mode 100644 Tests.Systran/appsettings.json.example diff --git a/.gitignore b/.gitignore index f18b9f8..582643d 100644 --- a/.gitignore +++ b/.gitignore @@ -365,3 +365,12 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd +/ConsoleApp1/ConsoleApp1.csproj +/ConsoleApp1/Program.cs +/SystranTests/appsettings.json +/SystranTests/TestFiles/676d81b85f6f31e36a019213 +/SystranTests/TestFiles/test.tmx +/SystranTests/TestFiles/Translate.txt +/SystranTests/TestFiles/Translate_output.txt +/SystranTests/TestFiles/Translate_output_translated.txt +/Tests.Systran/appsettings.json diff --git a/App.Systran.sln b/App.Systran.sln new file mode 100644 index 0000000..5065cd3 --- /dev/null +++ b/App.Systran.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35527.113 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apps.Systran", "Apps.Systran\Apps.Systran.csproj", "{EB25604E-8EBA-464E-BEDF-30EA95395AD3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Systran", "Tests.Systran\Tests.Systran.csproj", "{0EEEE9C9-A8D7-4A38-8416-C875E9CF0CFE}" + ProjectSection(ProjectDependencies) = postProject + {EB25604E-8EBA-464E-BEDF-30EA95395AD3} = {EB25604E-8EBA-464E-BEDF-30EA95395AD3} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Release|Any CPU.Build.0 = Release|Any CPU + {0EEEE9C9-A8D7-4A38-8416-C875E9CF0CFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EEEE9C9-A8D7-4A38-8416-C875E9CF0CFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EEEE9C9-A8D7-4A38-8416-C875E9CF0CFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EEEE9C9-A8D7-4A38-8416-C875E9CF0CFE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/App.sln b/App.sln deleted file mode 100644 index db41e80..0000000 --- a/App.sln +++ /dev/null @@ -1,15 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apps.App", "Apps.App\Apps.App.csproj", "{EB25604E-8EBA-464E-BEDF-30EA95395AD3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB25604E-8EBA-464E-BEDF-30EA95395AD3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/Apps.App/Actions/Actions.cs b/Apps.App/Actions/Actions.cs deleted file mode 100644 index 0d785c3..0000000 --- a/Apps.App/Actions/Actions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Apps.App.Invocables; -using Blackbird.Applications.Sdk.Common.Actions; -using Blackbird.Applications.Sdk.Common.Invocation; - -namespace Apps.App.Actions; - -[ActionList] -public class Actions(InvocationContext invocationContext) : AppInvocable(invocationContext) -{ - -} \ No newline at end of file diff --git a/Apps.App/Api/AppClient.cs b/Apps.App/Api/AppClient.cs deleted file mode 100644 index e3a6118..0000000 --- a/Apps.App/Api/AppClient.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Blackbird.Applications.Sdk.Common.Authentication; - -namespace Apps.App.Api; - -public class AppClient -{ - public AppClient(IEnumerable authenticationCredentialsProviders) - { - - } -} \ No newline at end of file diff --git a/Apps.App/Apps.App.csproj b/Apps.App/Apps.App.csproj deleted file mode 100644 index 4ce6adb..0000000 --- a/Apps.App/Apps.App.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net8.0 - enable - enable - App - Description - 1.0.0 - Apps.App - - - - - - - - - - - - diff --git a/Apps.App/Connections/ConnectionValidator.cs b/Apps.App/Connections/ConnectionValidator.cs deleted file mode 100644 index d494cf6..0000000 --- a/Apps.App/Connections/ConnectionValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Blackbird.Applications.Sdk.Common.Authentication; -using Blackbird.Applications.Sdk.Common.Connections; - -namespace Apps.App.Connections; - -public class ConnectionValidator: IConnectionValidator -{ - public async ValueTask ValidateConnection( - IEnumerable authenticationCredentialsProviders, - CancellationToken cancellationToken) - { - return new() - { - IsValid = true - }; - } -} \ No newline at end of file diff --git a/Apps.App/Constants/CredsNames.cs b/Apps.App/Constants/CredsNames.cs deleted file mode 100644 index 277749d..0000000 --- a/Apps.App/Constants/CredsNames.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Apps.App.Constants; - -public static class CredsNames -{ - public const string Token = "token"; -} \ No newline at end of file diff --git a/Apps.Systran/Actions/Base/BaseActions.cs b/Apps.Systran/Actions/Base/BaseActions.cs new file mode 100644 index 0000000..3347b35 --- /dev/null +++ b/Apps.Systran/Actions/Base/BaseActions.cs @@ -0,0 +1,20 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; + +namespace Apps.Systran.Actions.Base +{ + public abstract class BaseActions : SystranInvocable + { + protected readonly SystranClient Client; + protected readonly IFileManagementClient FileManagementClient; + + protected BaseActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : base(invocationContext) + { + Client = new SystranClient(invocationContext.AuthenticationCredentialsProviders); + FileManagementClient = fileManagementClient; + } + } +} diff --git a/Apps.Systran/Actions/CorpusActions.cs b/Apps.Systran/Actions/CorpusActions.cs new file mode 100644 index 0000000..6d0ea5c --- /dev/null +++ b/Apps.Systran/Actions/CorpusActions.cs @@ -0,0 +1,82 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models.Request; +using Apps.Systran.Models.Response; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using RestSharp; + +namespace Apps.Systran.Actions +{ + [ActionList] + public class CorpusActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : SystranInvocable(invocationContext) + { + [Action("Export corpus", Description = "Export a corpus as TMX file by its ID")] + public async Task ExportCorpus([ActionParameter] ExportCorpusParameters parameters) + { + var request = new SystranRequest($"/resources/corpus/export", RestSharp.Method.Get); + request.AddQueryParameter("corpusId", parameters.CorpusId); + + var response = await Client.DownloadStreamAsync(request); + if (response == null || !response.CanRead) + throw new PluginApplicationException("Failed to export corpus. Response is null or file is empty."); + + var fileName = parameters.CorpusId.EndsWith(".tmx", StringComparison.OrdinalIgnoreCase) + ? parameters.CorpusId + : $"{parameters.CorpusId}.tmx"; + + using (var memoryStream = new MemoryStream()) + { + await response.CopyToAsync(memoryStream); + memoryStream.Position = 0; + + var fileReference = await fileManagementClient.UploadAsync( + memoryStream, + "application/x-tmx+xml", + fileName); + + return new FileReferenceResponse + { + FileResponse = fileReference + }; + } + } + + [Action("Import corpus from TMX file", Description = "Add a new corpus from an TMX file")] + public async Task ImportCorpus([ActionParameter] ImportCorpusParameters parameters) + { + if (parameters.InputFile == null) + throw new PluginMisconfigurationException("Input file must be provided."); + + var fileStream = await fileManagementClient.DownloadAsync(parameters.InputFile); + var fileName = parameters.InputFile.Name.EndsWith(".tmx", StringComparison.OrdinalIgnoreCase) + ? parameters.InputFile.Name + : $"{parameters.InputFile.Name}.tmx"; + + var request = new SystranRequest($"/resources/corpus/import", RestSharp.Method.Post) + { + AlwaysMultipartFormData = true + }; + + request.AddQueryParameter("name", parameters.Name); + request.AddQueryParameter("format", "application/x-tmx+xml"); + + if (parameters.Tag != null && parameters.Tag.Any()) + { + foreach (var tag in parameters.Tag) + { + request.AddQueryParameter("tag", tag); + } + } + + request.AddFile("inputFile", () => fileStream, fileName, "application/x-tmx+xml"); + + var response = await Client.ExecuteWithErrorHandling(request); + + return response; + } + } +} diff --git a/Apps.Systran/Actions/DictionaryActions.cs b/Apps.Systran/Actions/DictionaryActions.cs new file mode 100644 index 0000000..40bb05a --- /dev/null +++ b/Apps.Systran/Actions/DictionaryActions.cs @@ -0,0 +1,190 @@ +using System.Text; +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models; +using Apps.Systran.Models.Request; +using Apps.Systran.Models.Response; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Files; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Glossaries.Utils.Converters; +using Blackbird.Applications.Sdk.Glossaries.Utils.Dtos; +using Blackbird.Applications.Sdk.Glossaries.Utils.Dtos.Enums; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using RestSharp; + +namespace Apps.Systran.Actions +{ + [ActionList] + public class DictionaryActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : SystranInvocable(invocationContext) + { + [Action("Create dictionary", Description = "Create a dictionary and populate it using a TBX file")] + public async Task CreateDictionary( + [ActionParameter] CreateDictionaryParameters parameters, + [ActionParameter] TranslateLanguagesOptions options) + { + var createDictionaryRequest = new SystranRequest("/resources/dictionary/add", Method.Post); + createDictionaryRequest.AddJsonBody(new + { + dictionary = new + { + name = parameters.Name, + sourceLang = options.Source, + sourcePos = parameters.SourcePos, + targetLangs = options.Target, + type = parameters.Type, + comments = parameters.Comment ?? "Created via API and populated using a TBX file" + } + }); + + var createResponse = await Client.ExecuteWithErrorHandling(createDictionaryRequest); + + var dictionaryId = createResponse.Added.Id; + + var systranFormattedContent = await ConvertTbxToSystranFormat(parameters.TbxFile, options.Source, options.Target); + + var importEntriesRequest = new SystranRequest("/resources/dictionary/entry/import", Method.Post); + importEntriesRequest.AddQueryParameter("dictionaryId", dictionaryId); + importEntriesRequest.AddQueryParameter("sourceLang", options.Source); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(systranFormattedContent))) + { + stream.Position = 0; + importEntriesRequest.AddFile("inputFile", + stream.ToArray(), + "ConvertedDictionary.tsv", + "text/plain"); + } + + var importResponse = await Client.ExecuteWithErrorHandling(importEntriesRequest); + + if (importResponse.Error != null) + throw new PluginApplicationException($"Failed to import entries: {importResponse.Error.Message}"); + + return createResponse; + } + + private async Task ConvertTbxToSystranFormat(FileReference tbxFile, string sourceLang, string targetLang) + { + await using var tbxStream = await fileManagementClient.DownloadAsync(tbxFile); + var blackbirdGlossary = await tbxStream.ConvertFromTbx(); + + var entries = new StringBuilder(); + entries.AppendLine("#ENCODING=UTF-8"); + entries.AppendLine($"#SUMMARY={tbxFile.Name}"); + entries.AppendLine($"#DESCRIPTION={blackbirdGlossary.ConceptEntries}"); + entries.AppendLine("#MULTI"); + entries.AppendLine($"#{sourceLang.ToUpper()}\tUPOS\t{targetLang.ToUpper()}\tPRIORITY_{targetLang.ToUpper()}\tCOMMENTS_{targetLang.ToUpper()}"); + + foreach (var entry in blackbirdGlossary.ConceptEntries) + { + var langSectionSource = entry.LanguageSections.FirstOrDefault(x => x.LanguageCode.ToLower() == sourceLang.ToLower()); + var langSectionTarget = entry.LanguageSections.FirstOrDefault(x => x.LanguageCode.ToLower() == targetLang.ToLower()); + + if (langSectionSource != null && langSectionTarget != null) + { + var sourceTerm = langSectionSource.Terms.FirstOrDefault()?.Term; + var targetTerm = langSectionTarget.Terms.FirstOrDefault()?.Term; + + if (!string.IsNullOrWhiteSpace(sourceTerm) && !string.IsNullOrWhiteSpace(targetTerm)) + { + entries.AppendLine($"{sourceTerm}\tnoun\t{targetTerm}\t9\t"); + } + } + } + + string tsvContent = entries.ToString(); + + return tsvContent; + } + + [Action("Export dictionary", Description = "Export dictionary as TBX file")] + public async Task ExportDictionary([ActionParameter] ExportDictionaryRequest parameters) + { + var listEntriesRequest = new SystranRequest("/resources/dictionary/entry/list", Method.Post); + listEntriesRequest.AddQueryParameter("dictionaryId", parameters.DictionaryId); + + var listEntriesResponse = await Client.ExecuteWithErrorHandling(listEntriesRequest); + + if (listEntriesResponse.Entries == null || !listEntriesResponse.Entries.Any()) + { + throw new PluginApplicationException($"No entries found for dictionary ID: {parameters.DictionaryId}"); + } + + var conceptEntries = listEntriesResponse.Entries.Select(entry => + new GlossaryConceptEntry(entry.SourceId, new List + { + new GlossaryLanguageSection(entry.SourceLang, new List + { + new GlossaryTermSection(entry.Source) + { + PartOfSpeech = Enum.TryParse(entry.PartOfSpeech, true, out var sourcePos) ? sourcePos : null + } + }), + new GlossaryLanguageSection(entry.TargetLang, new List + { + new GlossaryTermSection(entry.Target) + { + PartOfSpeech = Enum.TryParse(entry.PartOfSpeech, true, out var targetPos) ? targetPos : null + } + }) + })).ToList(); + + var glossary = new Glossary(conceptEntries) + { + Title = $"{parameters.DictionaryId}", + SourceDescription = $"Exported from dictionary ID: {parameters.DictionaryId}" + }; + + using var tbxStream = glossary.ConvertToTbx(); + + var fileReference = await fileManagementClient.UploadAsync( + tbxStream, + "application/x-tbx+xml", + $"{parameters.DictionaryId}.tbx"); + + return new FileReferenceResponse + { + FileResponse = fileReference + }; + } + + [Action("Update dictionary from TBX file", Description = "Update an existing dictionary by importing entries from a TBX file")] + public async Task UpdateDictionary( + [ActionParameter] UpdateDictionaryRequest parameters, + [ActionParameter] TranslateLanguagesOptions options) + { + var systranFormattedContent = await ConvertTbxToSystranFormat(parameters.File, options.Source, options.Target); + + var importEntriesRequest = new SystranRequest("/resources/dictionary/entry/import", Method.Post); + importEntriesRequest.AddQueryParameter("dictionaryId", parameters.DictionaryId); + importEntriesRequest.AddQueryParameter("sourceLang", options.Source); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(systranFormattedContent))) + { + stream.Position = 0; + importEntriesRequest.AddFile( + "inputFile", + stream.ToArray(), + "ConvertedDictionary.tsv", + "text/plain" + ); + } + + var importResponse = await Client.ExecuteWithErrorHandling(importEntriesRequest); + + if (importResponse.Error != null) + { + throw new PluginApplicationException($"Failed to import entries: {importResponse.Error.Message}"); + } + + return new UpdateDictionaryResponse + { + Success = true, + Message = $"Successfully updated dictionary ID: {parameters.DictionaryId}" + }; + } + } +} diff --git a/Apps.Systran/Actions/TranslateFileActions.cs b/Apps.Systran/Actions/TranslateFileActions.cs new file mode 100644 index 0000000..ab57d9d --- /dev/null +++ b/Apps.Systran/Actions/TranslateFileActions.cs @@ -0,0 +1,164 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models; +using Apps.Systran.Models.Request; +using Apps.Systran.Models.Response; +using Apps.Systran.Polling.Models; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using RestSharp; + +namespace Apps.Systran.Actions +{ + [ActionList] + public class TranslateFileActions : SystranInvocable + { + private readonly IFileManagementClient _fileManagementClient; + + public TranslateFileActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient) + : base(invocationContext) + { + _fileManagementClient = fileManagementClient; + } + + public static readonly List InputFormats = new() + { + "application/vnd.android.string-resource+xml", + "text/bitext", + "text/html", + "text/htm", + "application/xhtml+xml", + "application/vnd.systran.i18n+json", + "text/x-java-properties", + "application/vnd.microsoft.net.resx+xml", + "text/rtf", + "text/plain", + "xml/xliff", + "xml/xlf", + "application/x-tmx+xml", + "application/vnd.openxmlformats", + "application/vnd.oasis.opendocument", + "application/x-subrip", + "application/pdf", + "application/msword", + "image/bmp", + "image/jpeg", + "image/png", + "image/tiff" + }; + + [Action("Translate file", Description = "Translate a file from source language to target language")] + public async Task TranslateFile( + [ActionParameter] TranslateLanguagesOptions options, + [ActionParameter] TranslateFileRequest input) + { + var inputType = input.Input.ContentType; + if (!InputFormats.Contains(inputType)) + { + throw new PluginMisconfigurationException($"Unsupported file format: {inputType}. Please provide a file with one of the supported formats."); + } + + var request = new SystranRequest("/translation/file/translate", Method.Post) + { + AlwaysMultipartFormData = true + }; + + if (!string.IsNullOrEmpty(options.Source)) + request.AddQueryParameter("source", options.Source); + + if (!string.IsNullOrEmpty(options.Target)) + request.AddQueryParameter("target", options.Target); + + if (!string.IsNullOrEmpty(input.Profile)) + request.AddQueryParameter("profile", input.Profile); + + var fileStream = await _fileManagementClient.DownloadAsync(input.Input); + + request.AddFile("input", () => fileStream, input.Input.Name); + + var rawResponse = await Client.ExecuteAsync(request); + + if (!rawResponse.IsSuccessful || rawResponse.RawBytes == null) + { + throw new PluginApplicationException($"Failed to translate file. Status: {rawResponse.StatusCode}, Error: {rawResponse.ErrorMessage}"); + } + + var translatedFile = await _fileManagementClient.UploadAsync( + new MemoryStream(rawResponse.RawBytes), + rawResponse.ContentType, + $"{Path.GetFileNameWithoutExtension(input.Input.Name)}_translated{Path.GetExtension(input.Input.Name)}"); + + return new FileReferenceResponse { FileResponse = translatedFile }; + } + + [Action("Translate file (Async)", Description = "Translate a file from source language to target language")] + public async Task TranslateFileAsync( + [ActionParameter] TranslateLanguagesOptions options, + [ActionParameter] TranslateFileRequest input) + { + var inputType = input.Input.ContentType; + if (!InputFormats.Contains(inputType)) + { + throw new PluginMisconfigurationException($"Unsupported file format: {inputType}. Please provide a file with one of the supported formats."); + } + + var request = new SystranRequest("/translation/file/translate", Method.Post) + { + AlwaysMultipartFormData = true + }; + + if (!string.IsNullOrEmpty(options.Source)) + request.AddQueryParameter("source", options.Source); + + if (!string.IsNullOrEmpty(options.Target)) + request.AddQueryParameter("target", options.Target); + + if (!string.IsNullOrEmpty(input.Profile)) + request.AddQueryParameter("profile", input.Profile); + + request.AddQueryParameter("async", true); + + using var fileStream = await _fileManagementClient.DownloadAsync(input.Input); + + request.AddFile("input", () => fileStream, input.Input.Name); + + var rawResponse = await Client.ExecuteWithErrorHandling(request); + + return new TranslateFileAsyncResponse { RequestId = rawResponse.RequestId }; + } + + [Action("Download translated file", Description = "Download a translated file by request ID")] + public async Task DownloadTranslatedFile([ActionParameter] string requestId) + { + var statusRequest = new SystranRequest("/translation/file/status", Method.Get); + statusRequest.AddQueryParameter("requestId", requestId); + + var statusResponse = await Client.ExecuteAsync(statusRequest); + + if (statusResponse.Data == null || statusResponse.Data.Status != "finished") + { + throw new PluginApplicationException("Translation is not finished yet or request ID is invalid."); + } + + var resultRequest = new SystranRequest("/translation/file/result", Method.Get); + resultRequest.AddQueryParameter("requestId", requestId); + + var rawResponse = await Client.ExecuteAsync(resultRequest); + + if (!rawResponse.IsSuccessful || rawResponse.RawBytes == null) + { + throw new PluginApplicationException($"Failed to retrieve the translation result. Status: {rawResponse.StatusCode}, Error: {rawResponse.ErrorMessage}"); + } + + var translatedFile = await _fileManagementClient.UploadAsync( + new MemoryStream(rawResponse.RawBytes), + rawResponse.ContentType, + $"{requestId}"); + + return new FileReferenceResponse { FileResponse = translatedFile }; + } + } +} diff --git a/Apps.Systran/Actions/TranslateTextActions.cs b/Apps.Systran/Actions/TranslateTextActions.cs new file mode 100644 index 0000000..00e8dec --- /dev/null +++ b/Apps.Systran/Actions/TranslateTextActions.cs @@ -0,0 +1,56 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models; +using Apps.Systran.Models.Request; +using Apps.Systran.Models.Response; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Actions; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; +using RestSharp; + +namespace Apps.App.Actions; + +[ActionList] +public class TranslateTextActions(InvocationContext invocationContext) : SystranInvocable(invocationContext) +{ + + [Action("Translate text", Description = "Translate text")] + public async Task TranslateText([ActionParameter] TranslateLanguagesOptions options, + [ActionParameter] TranslateTextRequest input) + { + if (input.Input.Length > 50000 || GetInputSize(input.Input) > 50 * 1024 * 1024) + { + throw new PluginMisconfigurationException("Input exceeds the maximum allowed size of 50,000 paragraphs or 50 MB."); + } + + var request = new SystranRequest($"/translation/text/translate", RestSharp.Method.Post); + + request.AddQueryParameter("input", input.Input); + request.AddQueryParameter("source", options.Source ?? "auto"); + request.AddQueryParameter("target", options.Target); + + if (!string.IsNullOrEmpty(input.Profile)) + request.AddQueryParameter("profile", input.Profile); + + if (input.WithInfo.HasValue) + request.AddQueryParameter("withInfo", input.WithInfo.Value.ToString().ToLower()); + + if (input.WithSource.HasValue) + request.AddQueryParameter("withSource", input.WithSource.Value.ToString().ToLower()); + + if (input.WithAnnotations.HasValue) + request.AddQueryParameter("withAnnotations", input.WithAnnotations.Value.ToString().ToLower()); + + if (input.BackTranslation.HasValue) + request.AddQueryParameter("backTranslation", input.BackTranslation.Value.ToString().ToLower()); + + var response = await Client.ExecuteWithErrorHandling(request); + return response; + } + + private long GetInputSize(string input) + { + return System.Text.Encoding.UTF8.GetByteCount(input); + } +} \ No newline at end of file diff --git a/Apps.Systran/Api/SystranClient.cs b/Apps.Systran/Api/SystranClient.cs new file mode 100644 index 0000000..e4994b4 --- /dev/null +++ b/Apps.Systran/Api/SystranClient.cs @@ -0,0 +1,58 @@ +using Apps.App.Constants; +using Apps.App.Dto; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Utils.RestSharp; +using Newtonsoft.Json; +using RestSharp; + +namespace Apps.App.Api; + +public class SystranClient : BlackBirdRestClient +{ + public SystranClient(IEnumerable authenticationCredentialsProviders) : + base(new RestSharp.RestClientOptions { BaseUrl = GetUri(authenticationCredentialsProviders) }) + { + var apiKey = authenticationCredentialsProviders + .First(x => x.KeyName == CredsNames.ApiKey).Value; + + this.AddDefaultHeader("Authorization", $"Key {apiKey}"); + } + + public static Uri GetUri(IEnumerable authenticationCredentials) + { + return new Uri($"{authenticationCredentials.First(x => x.KeyName == CredsNames.Url).Value}"); + } + + protected override Exception ConfigureErrorException(RestResponse response) + { + var errors = JsonConvert.DeserializeObject(response.Content!)!; + + if (errors != null && !string.IsNullOrEmpty(errors.Message)) + { + return new($"Systran API Error: {errors.Message}"); + } + + return new("Unknown error"); + } + + public override async Task ExecuteWithErrorHandling(RestRequest request) + { + + var restResponse = await ExecuteAsync(request); + if (!restResponse.IsSuccessStatusCode) + throw ConfigureErrorException(restResponse); + try + { + var error = JsonConvert.DeserializeObject(restResponse.Content!); + if (error is { HasError: true }) + throw new PluginMisconfigurationException($"Systran error: {error.Message}"); + } + catch (PluginMisconfigurationException) + { + throw; + } + + return restResponse; + } +} \ No newline at end of file diff --git a/Apps.Systran/Api/SystranRequest.cs b/Apps.Systran/Api/SystranRequest.cs new file mode 100644 index 0000000..c958dd8 --- /dev/null +++ b/Apps.Systran/Api/SystranRequest.cs @@ -0,0 +1,11 @@ +using RestSharp; + +namespace Apps.App.Api +{ + public class SystranRequest : RestRequest + { + public SystranRequest(string resource, Method method) : base(resource, method) + { + } + } +} diff --git a/Apps.App/Application.cs b/Apps.Systran/Application.cs similarity index 84% rename from Apps.App/Application.cs rename to Apps.Systran/Application.cs index 9873387..f8547dc 100644 --- a/Apps.App/Application.cs +++ b/Apps.Systran/Application.cs @@ -7,7 +7,7 @@ public class Application : IApplication, ICategoryProvider { public IEnumerable Categories { - get => []; + get => [ApplicationCategory.MachineTranslationAndMtqe]; set { } } diff --git a/Apps.Systran/Apps.Systran.csproj b/Apps.Systran/Apps.Systran.csproj new file mode 100644 index 0000000..f7c59d3 --- /dev/null +++ b/Apps.Systran/Apps.Systran.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + Systran [Beta] + SYSTRAN Translate API is built around a RESTful API and can be used in all types of applications.This API Reference is intended for developers who want to write applications that can interact with the SYSTRAN Translate API. You can integrate our translation technology directly in your internal or external applications to make them multilingual or translate texts and files by using the SYSTRAN Translate API. + 1.0.1 + Apps.Systran + + + + + + + + + + + + + diff --git a/Apps.App/Connections/ConnectionDefinition.cs b/Apps.Systran/Connections/ConnectionDefinition.cs similarity index 81% rename from Apps.App/Connections/ConnectionDefinition.cs rename to Apps.Systran/Connections/ConnectionDefinition.cs index de794df..2df6841 100644 --- a/Apps.App/Connections/ConnectionDefinition.cs +++ b/Apps.Systran/Connections/ConnectionDefinition.cs @@ -10,11 +10,12 @@ public class ConnectionDefinition : IConnectionDefinition { new() { - Name = "Developer API key", + Name = "Api Key", AuthenticationType = ConnectionAuthenticationType.Undefined, ConnectionProperties = new List { - new(CredsNames.Token) { DisplayName = "API Token", Sensitive = true} + new(CredsNames.Url) { DisplayName = "Instance URL" }, + new(CredsNames.ApiKey) { DisplayName = "API Key", Sensitive = true} } } }; diff --git a/Apps.Systran/Connections/ConnectionValidator.cs b/Apps.Systran/Connections/ConnectionValidator.cs new file mode 100644 index 0000000..4bebe55 --- /dev/null +++ b/Apps.Systran/Connections/ConnectionValidator.cs @@ -0,0 +1,32 @@ +using Apps.App.Api; +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Connections; +using RestSharp; + +namespace Apps.App.Connections; + +public class ConnectionValidator: IConnectionValidator +{ + public async ValueTask ValidateConnection( + IEnumerable authenticationCredentialsProviders, + CancellationToken cancellationToken) + { + try + { + var request = new SystranRequest("/translation/apiVersion", Method.Get); + var response = await new SystranClient(authenticationCredentialsProviders).ExecuteWithErrorHandling(request); + return new() + { + IsValid = true, + }; + } + catch(Exception ex) + { + return new() + { + IsValid = false, + Message = ex.Message + }; + } + } +} \ No newline at end of file diff --git a/Apps.Systran/Constants/CredsNames.cs b/Apps.Systran/Constants/CredsNames.cs new file mode 100644 index 0000000..d6df499 --- /dev/null +++ b/Apps.Systran/Constants/CredsNames.cs @@ -0,0 +1,7 @@ +namespace Apps.App.Constants; + +public static class CredsNames +{ + public const string Url = "url"; + public const string ApiKey = "apiKey"; +} \ No newline at end of file diff --git a/Apps.Systran/DataSourceHandlers/CorporaDataHandler.cs b/Apps.Systran/DataSourceHandlers/CorporaDataHandler.cs new file mode 100644 index 0000000..863a044 --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/CorporaDataHandler.cs @@ -0,0 +1,28 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models.Response; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Systran.DataSourceHandlers +{ + public class CorporaDataHandler : SystranInvocable, IAsyncDataSourceItemHandler + { + public CorporaDataHandler(InvocationContext invocationContext) : base(invocationContext) { } + + public async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) + { + var request = new SystranRequest("/resources/corpus/list", RestSharp.Method.Get); + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Files + .Where(corpus => string.IsNullOrWhiteSpace(context.SearchString) || + corpus.Name.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .Take(50) + .Select(corpus => new DataSourceItem( + corpus.Id, + $"{corpus.Name} ({corpus.SourceLang} -> {string.Join(", ", corpus.TargetLangs ?? Enumerable.Empty())})" + )); + } + } +} diff --git a/Apps.Systran/DataSourceHandlers/DictionaryDataHandler.cs b/Apps.Systran/DataSourceHandlers/DictionaryDataHandler.cs new file mode 100644 index 0000000..561f73e --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/DictionaryDataHandler.cs @@ -0,0 +1,27 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models.Response; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Systran.DataSourceHandlers +{ + public class DictionaryDataHandler : SystranInvocable, IAsyncDataSourceItemHandler + { + public DictionaryDataHandler(InvocationContext invocationContext) : base(invocationContext) { } + public async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) + { + var request = new SystranRequest("/resources/dictionary/list", RestSharp.Method.Post); + var response = await Client.ExecuteWithErrorHandling(request); + + return response.Dictionaries + .Where(dictionary => string.IsNullOrWhiteSpace(context.SearchString) || + dictionary.Name.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .Take(50) + .Select(dictionary => new DataSourceItem( + dictionary.Id, + $"{dictionary.Name} ({dictionary.SourceLang} -> {dictionary.TargetLangs})" + )); + } + } +} diff --git a/Apps.Systran/DataSourceHandlers/EnumDataHandlers/CorpusFormatDataHandler .cs b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/CorpusFormatDataHandler .cs new file mode 100644 index 0000000..62c568c --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/CorpusFormatDataHandler .cs @@ -0,0 +1,23 @@ +using Blackbird.Applications.Sdk.Common.Dictionaries; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.DataSourceHandlers.EnumDataHandlers +{ + public class CorpusFormatDataHandler : IStaticDataSourceItemHandler + { + protected Dictionary EnumValues => new() + { + { "application/x-tmx+xml", "TMX XML" }, + { "text/bitext", "Bitext" } + }; + + public IEnumerable GetData() + { + return EnumValues.Select(item => new DataSourceItem + { + Value = item.Key, + DisplayName = item.Value + }); + } + } +} diff --git a/Apps.Systran/DataSourceHandlers/EnumDataHandlers/DictionaryTypeDataHandler .cs b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/DictionaryTypeDataHandler .cs new file mode 100644 index 0000000..f57c7b8 --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/DictionaryTypeDataHandler .cs @@ -0,0 +1,23 @@ +using Blackbird.Applications.Sdk.Common.Dictionaries; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.DataSourceHandlers.EnumDataHandlers +{ + public class DictionaryTypeDataHandler : IStaticDataSourceItemHandler + { + protected Dictionary EnumValues => new() + { + {"UD", "User Dictionary"}, + {"NORM", "Normalization Dictionary"} + }; + + public IEnumerable GetData() + { + return EnumValues.Select(item => new DataSourceItem + { + Value = item.Key, + DisplayName = item.Value + }); + } + } +} diff --git a/Apps.Systran/DataSourceHandlers/EnumDataHandlers/LanguageCodeDataHandler.cs b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/LanguageCodeDataHandler.cs new file mode 100644 index 0000000..fa685cd --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/LanguageCodeDataHandler.cs @@ -0,0 +1,180 @@ +using Blackbird.Applications.Sdk.Common.Dictionaries; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.DataSourceHandlers.EnumDataHandlers +{ + public class LanguageCodeDataHandler : IStaticDataSourceItemHandler + { + protected Dictionary LanguageCodes => new() + { + {"aa", "Afar"},{"ab", "Abkhazian"},{"af", "Afrikaans"}, + {"ak", "Akan"},{"sq", "Albanian"},{"am", "Amharic"}, + {"ar", "Arabic"},{"an", "Aragonese"},{"hy", "Armenian"}, + {"as", "Assamese"},{"av", "Avaric"},{"ae", "Avestan"}, + {"ay", "Aymara"},{"az", "Azerbaijani"},{"ba", "Bashkir"}, + {"bm", "Bambara"}, + {"eu", "Basque"}, + {"be", "Belarusian"}, + {"bn", "Bengali"}, + {"bh", "Bihari"}, + {"bi", "Bislama"}, + {"bs", "Bosnian"}, + {"br", "Breton"}, + {"bg", "Bulgarian"}, + {"my", "Burmese"}, + {"ca", "Catalan"}, + {"ch", "Chamorro"}, + {"ce", "Chechen"}, + {"zh", "Chinese"}, + {"cu", "Church Slavic"}, + {"cv", "Chuvash"}, + {"kw", "Cornish"}, + {"co", "Corsican"}, + {"cr", "Cree"}, + {"hr", "Croatian"}, + {"cs", "Czech"}, + {"da", "Danish"}, + {"dv", "Divehi"}, + {"nl", "Dutch"}, + {"en", "English"}, + {"eo", "Esperanto"}, + {"et", "Estonian"}, + {"ee", "Ewe"}, + {"fo", "Faroese"}, + {"fj", "Fijian"}, + {"fi", "Finnish"}, + {"fr", "French"}, + {"fy", "Western Frisian"}, + {"ff", "Fulah"}, + {"ka", "Georgian"}, + {"de", "German"}, + {"el", "Greek"}, + {"gn", "Guarani"}, + {"gu", "Gujarati"}, + {"ht", "Haitian"}, + {"ha", "Hausa"}, + {"he", "Hebrew"}, + {"hz", "Herero"}, + {"hi", "Hindi"}, + {"ho", "Hiri Motu"}, + {"hu", "Hungarian"}, + {"ia", "Interlingua"}, + {"id", "Indonesian"}, + {"ie", "Interlingue"}, + {"ga", "Irish"}, + {"ig", "Igbo"}, + {"ik", "Inupiaq"}, + {"io", "Ido"}, + {"is", "Icelandic"}, + {"it", "Italian"}, + {"iu", "Inuktitut"}, + {"ja", "Japanese"}, + {"jv", "Javanese"}, + {"kl", "Kalaallisut"}, + {"kn", "Kannada"}, + {"kr", "Kanuri"}, + {"ks", "Kashmiri"}, + {"kk", "Kazakh"}, + {"km", "Central Khmer"}, + {"ki", "Kikuyu"}, + {"rw", "Kinyarwanda"}, + {"ky", "Kirghiz"}, + {"kv", "Komi"}, + {"kg", "Kongo"}, + {"ko", "Korean"}, + {"kj", "Kuanyama"}, + {"ku", "Kurdish"}, + {"lo", "Lao"}, + {"la", "Latin"}, + {"lv", "Latvian"}, + {"li", "Limburgan"}, + {"ln", "Lingala"}, + {"lt", "Lithuanian"}, + {"lu", "Luba-Katanga"}, + {"lb", "Luxembourgish"}, + {"mk", "Macedonian"}, + {"mg", "Malagasy"}, + {"ms", "Malay"}, + {"ml", "Malayalam"}, + {"mt", "Maltese"}, + {"mi", "Maori"}, + {"mr", "Marathi"}, + {"mh", "Marshallese"}, + {"mn", "Mongolian"}, + {"na", "Nauru"}, + {"nv", "Navajo"}, + {"nd", "North Ndebele"}, + {"nr", "South Ndebele"}, + {"ng", "Ndonga"}, + {"ne", "Nepali"}, + {"no", "Norwegian"}, + {"nb", "Norwegian Bokmal"}, + {"nn", "Norwegian Nynorsk"}, + {"ii", "Sichuan Yi"}, + {"oc", "Occitan"}, + {"oj", "Ojibwa"}, + {"or", "Oriya"}, + {"om", "Oromo"}, + {"os", "Ossetian"}, + {"pi", "Pali"}, + {"pa", "Panjabi"}, + {"ps", "Pashto"}, + {"fa", "Persian"}, + {"pl", "Polish"}, + {"pt", "Portuguese"}, + {"qu", "Quechua"}, + {"ro", "Romanian"}, + {"rm", "Romansh"}, + {"rn", "Rundi"}, + {"ru", "Russian"}, + {"sm", "Samoan"}, + {"sg", "Sango"}, + {"sa", "Sanskrit"}, + {"sc", "Sardinian"}, + {"sr", "Serbian"}, + {"sn", "Shona"}, + {"sd", "Sindhi"}, + {"si", "Sinhala"}, + {"sk", "Slovak"}, + {"sl", "Slovenian"}, + {"so", "Somali"}, + {"st", "Southern Sotho"}, + {"es", "Spanish"}, + {"su", "Sundanese"}, + {"sw", "Swahili"}, + {"ss", "Swati"}, + {"sv", "Swedish"}, + {"ta", "Tamil"}, + {"te", "Telugu"}, + {"tg", "Tajik"}, + {"th", "Thai"}, + {"ti", "Tigrinya"}, + {"to", "Tonga"}, + {"tr", "Turkish"}, + {"tk", "Turkmen"}, + {"tw", "Twi"}, + {"uk", "Ukrainian"}, + {"ur", "Urdu"}, + {"uz", "Uzbek"}, + {"vi", "Vietnamese"}, + {"vo", "Volapük"}, + {"wa", "Walloon"}, + {"cy", "Welsh"}, + {"wo", "Wolof"}, + {"xh", "Xhosa"}, + {"yi", "Yiddish"}, + {"yo", "Yoruba"}, + {"za", "Zhuang"}, + {"zu", "Zulu"} + }; + + public IEnumerable GetData() + { + return LanguageCodes.Select(language => new DataSourceItem + { + Value = language.Key, + DisplayName = language.Value + }); + } + } +} diff --git a/Apps.Systran/DataSourceHandlers/EnumDataHandlers/SourcePosDataHandler .cs b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/SourcePosDataHandler .cs new file mode 100644 index 0000000..83e80b3 --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/EnumDataHandlers/SourcePosDataHandler .cs @@ -0,0 +1,31 @@ +using Blackbird.Applications.Sdk.Common.Dictionaries; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.DataSourceHandlers.EnumDataHandlers +{ + public class SourcePosDataHandler : IStaticDataSourceItemHandler + { + protected Dictionary EnumValues => new() + { + {"acr", "Acronym"}, + {"adj", "Adjective"}, + {"adv", "Adverb"}, + {"conj", "Conjunction"}, + {"expr", "Expression"}, + {"noun", "Noun"}, + {"prep", "Preposition"}, + {"proper noun", "Proper Noun"}, + {"rule", "Rule"}, + {"verb", "Verb"} + }; + + public IEnumerable GetData() + { + return EnumValues.Select(item => new DataSourceItem + { + Value = item.Key, + DisplayName = item.Value + }); + } + } +} diff --git a/Apps.Systran/DataSourceHandlers/ProfilesDataHandler .cs b/Apps.Systran/DataSourceHandlers/ProfilesDataHandler .cs new file mode 100644 index 0000000..7dc3f4e --- /dev/null +++ b/Apps.Systran/DataSourceHandlers/ProfilesDataHandler .cs @@ -0,0 +1,32 @@ +using Apps.App.Api; +using Apps.App.Invocables; +using Apps.Systran.Models.Response; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Invocation; + +namespace Apps.Systran.DataSourceHandlers +{ + public class ProfilesDataHandler : SystranInvocable, IAsyncDataSourceItemHandler + { + public ProfilesDataHandler(InvocationContext invocationContext) : base(invocationContext) { } + + public async Task> GetDataAsync(DataSourceContext context, CancellationToken cancellationToken) + { + var request = new SystranRequest("/profiles", RestSharp.Method.Get); + var response = await Client.ExecuteWithErrorHandling(request); + + if (response?.Profiles == null || !response.Profiles.Any()) + { + throw new Exception("No profiles found."); + } + + return response.Profiles + .Where(profile => string.IsNullOrWhiteSpace(context.SearchString) || + profile.Name.Contains(context.SearchString, StringComparison.OrdinalIgnoreCase)) + .Select(profile => new DataSourceItem( + profile.Id, + $"{profile.Name} ({profile.Source} -> {profile.Target})" + )); + } + } +} diff --git a/Apps.Systran/Dto/SystranError.cs b/Apps.Systran/Dto/SystranError.cs new file mode 100644 index 0000000..0ac4a12 --- /dev/null +++ b/Apps.Systran/Dto/SystranError.cs @@ -0,0 +1,8 @@ +namespace Apps.App.Dto +{ + public class SystranError + { + public bool HasError => !string.IsNullOrEmpty(Message); + public string? Message { get; set; } + } +} diff --git a/Apps.Systran/Dto/SystranStatusResponse.cs b/Apps.Systran/Dto/SystranStatusResponse.cs new file mode 100644 index 0000000..3fd6806 --- /dev/null +++ b/Apps.Systran/Dto/SystranStatusResponse.cs @@ -0,0 +1,7 @@ +namespace Apps.App.Dto +{ + public class SystranStatusResponse + { + public string? Status { get; set; } + } +} diff --git a/Apps.Systran/Dto/TranslationStatusDto.cs b/Apps.Systran/Dto/TranslationStatusDto.cs new file mode 100644 index 0000000..eaeaa5d --- /dev/null +++ b/Apps.Systran/Dto/TranslationStatusDto.cs @@ -0,0 +1,8 @@ +namespace Apps.Systran.Dto +{ + public class TranslationStatusDto + { + public string Status { get; set; } + public DateTime FinishedAt { get; set; } + } +} diff --git a/Apps.App/Invocables/AppInvocable.cs b/Apps.Systran/Invocables/SystranInvocable.cs similarity index 67% rename from Apps.App/Invocables/AppInvocable.cs rename to Apps.Systran/Invocables/SystranInvocable.cs index 1dd06de..7192023 100644 --- a/Apps.App/Invocables/AppInvocable.cs +++ b/Apps.Systran/Invocables/SystranInvocable.cs @@ -5,13 +5,13 @@ namespace Apps.App.Invocables; -public class AppInvocable : BaseInvocable +public class SystranInvocable : BaseInvocable { protected AuthenticationCredentialsProvider[] Creds => InvocationContext.AuthenticationCredentialsProviders.ToArray(); - protected AppClient Client { get; } - public AppInvocable(InvocationContext invocationContext) : base(invocationContext) + protected SystranClient Client { get; set; } + public SystranInvocable(InvocationContext invocationContext) : base(invocationContext) { Client = new(Creds); } diff --git a/Apps.Systran/Models/Request/CreateDictionaryParameters.cs b/Apps.Systran/Models/Request/CreateDictionaryParameters.cs new file mode 100644 index 0000000..36ef6b5 --- /dev/null +++ b/Apps.Systran/Models/Request/CreateDictionaryParameters.cs @@ -0,0 +1,26 @@ +using Apps.Systran.DataSourceHandlers.EnumDataHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dictionaries; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Systran.Models.Request +{ + public class CreateDictionaryParameters + { + [Display("Dictionary name")] + public string Name { get; set; } + + [Display("Source pos")] + [StaticDataSource(typeof(SourcePosDataHandler))] + public string? SourcePos { get; set; } + + [Display("Dictionary type")] + [StaticDataSource(typeof(DictionaryTypeDataHandler))] + public string Type { get; set; } + + [Display("Dictionary description")] + public string? Comment { get; set; } + [Display("File")] + public FileReference TbxFile { get; set; } + } +} diff --git a/Apps.Systran/Models/Request/ExportCorpusParameters.cs b/Apps.Systran/Models/Request/ExportCorpusParameters.cs new file mode 100644 index 0000000..e28f220 --- /dev/null +++ b/Apps.Systran/Models/Request/ExportCorpusParameters.cs @@ -0,0 +1,13 @@ +using Apps.Systran.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.Models.Request +{ + public class ExportCorpusParameters + { + [Display("Corpus ID")] + [DataSource(typeof(CorporaDataHandler))] + public string CorpusId { get; set; } + } +} diff --git a/Apps.Systran/Models/Request/ExportDictionaryRequest.cs b/Apps.Systran/Models/Request/ExportDictionaryRequest.cs new file mode 100644 index 0000000..4b3a91d --- /dev/null +++ b/Apps.Systran/Models/Request/ExportDictionaryRequest.cs @@ -0,0 +1,13 @@ +using Apps.Systran.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.Models.Request +{ + public class ExportDictionaryRequest + { + [Display("Dictionary ID")] + [DataSource(typeof(DictionaryDataHandler))] + public string DictionaryId { get; set; } + } +} diff --git a/Apps.Systran/Models/Request/ImportCorpusParameters.cs b/Apps.Systran/Models/Request/ImportCorpusParameters.cs new file mode 100644 index 0000000..04e296e --- /dev/null +++ b/Apps.Systran/Models/Request/ImportCorpusParameters.cs @@ -0,0 +1,18 @@ +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Systran.Models.Request +{ + public class ImportCorpusParameters + { + [Display("Corpus name")] + public string Name { get; set; } + + [Display("Input file")] + public FileReference InputFile { get; set; } + + [Display("Tag")] + public string[]? Tag { get; set; } + + } +} diff --git a/Apps.Systran/Models/Request/TranslateFileRequest.cs b/Apps.Systran/Models/Request/TranslateFileRequest.cs new file mode 100644 index 0000000..e1e2b0b --- /dev/null +++ b/Apps.Systran/Models/Request/TranslateFileRequest.cs @@ -0,0 +1,17 @@ +using Apps.Systran.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Systran.Models.Request +{ + public class TranslateFileRequest + { + [Display("Input file")] + public FileReference Input { get; set; } + + [Display("Profile ID")] + [DataSource(typeof(ProfilesDataHandler))] + public string? Profile { get; set; } + } +} diff --git a/Apps.Systran/Models/Request/TranslateTextRequest.cs b/Apps.Systran/Models/Request/TranslateTextRequest.cs new file mode 100644 index 0000000..57a4551 --- /dev/null +++ b/Apps.Systran/Models/Request/TranslateTextRequest.cs @@ -0,0 +1,28 @@ +using Apps.Systran.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; + +namespace Apps.Systran.Models.Request +{ + public class TranslateTextRequest + { + [Display("Input text")] + public string Input { get; set; } + + [Display("Profile ID")] + [DataSource(typeof(ProfilesDataHandler))] + public string? Profile { get; set; } + + [Display("With info")] + public bool? WithInfo { get; set; } + + [Display("With source")] + public bool? WithSource { get; set; } + + [Display("With annotations")] + public bool? WithAnnotations { get; set; } + + [Display("Back translation")] + public bool? BackTranslation { get; set; } + } +} diff --git a/Apps.Systran/Models/Request/UpdateDictionaryRequest.cs b/Apps.Systran/Models/Request/UpdateDictionaryRequest.cs new file mode 100644 index 0000000..d176032 --- /dev/null +++ b/Apps.Systran/Models/Request/UpdateDictionaryRequest.cs @@ -0,0 +1,17 @@ +using Apps.Systran.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dynamic; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Systran.Models.Request +{ + public class UpdateDictionaryRequest + { + [Display("Dictionary ID")] + [DataSource(typeof(DictionaryDataHandler))] + public string DictionaryId { get; set; } + + [Display("TBX file")] + public FileReference File { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/CorporaListResponse.cs b/Apps.Systran/Models/Response/CorporaListResponse.cs new file mode 100644 index 0000000..2d5943f --- /dev/null +++ b/Apps.Systran/Models/Response/CorporaListResponse.cs @@ -0,0 +1,16 @@ +namespace Apps.Systran.Models.Response +{ + public class CorporaListResponse + { + public IEnumerable Files { get; set; } + public IEnumerable Directories { get; set; } + } + public class CorpusFile + { + public string Id { get; set; } + public string Name { get; set; } + public string SourceLang { get; set; } + public IEnumerable TargetLangs { get; set; } + public string Status { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/CreateDictionaryResponse.cs b/Apps.Systran/Models/Response/CreateDictionaryResponse.cs new file mode 100644 index 0000000..f2a41b8 --- /dev/null +++ b/Apps.Systran/Models/Response/CreateDictionaryResponse.cs @@ -0,0 +1,33 @@ +using Apps.App.Dto; +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Systran.Models.Response +{ + public class CreateDictionaryResponse + { + public SystranError? Error { get; set; } + + [Display("Added")] + public AddedDictionary Added { get; set; } + } + + public class AddedDictionary + { + [Display("Dictionary ID")] + public string Id { get; set; } + + [Display("Dictionary name")] + public string Name { get; set; } + + [Display("Source language code")] + public string SourceLang { get; set; } + + public string SourcePos { get; set; } + + [Display("Dictionary type")] + public string Type { get; set; } + + [Display("Dictionary description")] + public string Comments { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/DictionaryDataHandlerResponse.cs b/Apps.Systran/Models/Response/DictionaryDataHandlerResponse.cs new file mode 100644 index 0000000..1d9e4b3 --- /dev/null +++ b/Apps.Systran/Models/Response/DictionaryDataHandlerResponse.cs @@ -0,0 +1,19 @@ +namespace Apps.Systran.Models.Response +{ + public class DictionaryDataHandlerResponse + { + public ErrorResponse? Error { get; set; } + public int TotalNoLimit { get; set; } + public List Dictionaries { get; set; } = new(); + } + public class DictionaryItem + { + public string SourceLang { get; set; } = string.Empty; + public string TargetLangs { get; set; } = string.Empty; + public int NbEntries { get; set; } + public string Comments { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string Id { get; set; } = string.Empty; + } +} diff --git a/Apps.Systran/Models/Response/ErrorResponse.cs b/Apps.Systran/Models/Response/ErrorResponse.cs new file mode 100644 index 0000000..4951247 --- /dev/null +++ b/Apps.Systran/Models/Response/ErrorResponse.cs @@ -0,0 +1,9 @@ +namespace Apps.Systran.Models.Response +{ + public class ErrorResponse + { + public string? Message { get; set; } = string.Empty; + public int? StatusCode { get; set; } + public object? Info { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/FileReferenceResponse.cs b/Apps.Systran/Models/Response/FileReferenceResponse.cs new file mode 100644 index 0000000..3969195 --- /dev/null +++ b/Apps.Systran/Models/Response/FileReferenceResponse.cs @@ -0,0 +1,11 @@ +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Systran.Models.Response +{ + public class FileReferenceResponse + { + [Display("File")] + public FileReference FileResponse { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/ImportCorpusResponse.cs b/Apps.Systran/Models/Response/ImportCorpusResponse.cs new file mode 100644 index 0000000..bdb2876 --- /dev/null +++ b/Apps.Systran/Models/Response/ImportCorpusResponse.cs @@ -0,0 +1,17 @@ +using Blackbird.Applications.Sdk.Common; +using Newtonsoft.Json; + +namespace Apps.Systran.Models.Response +{ + public class ImportCorpusResponse + { + [JsonProperty("corpus")] + public CorpusInfo Corpus { get; set; } + } + public class CorpusInfo + { + [Display("Coprus ID")] + [JsonProperty("id")] + public string Id { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/ImportResponse.cs b/Apps.Systran/Models/Response/ImportResponse.cs new file mode 100644 index 0000000..c966e1c --- /dev/null +++ b/Apps.Systran/Models/Response/ImportResponse.cs @@ -0,0 +1,12 @@ +using Apps.App.Dto; + +namespace Apps.Systran.Models.Response +{ + public class ImportResponse + { + public SystranError? Error { get; set; } + public int Inserted { get; set; } + public int Duplicates { get; set; } + public int Total { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/ListEntriesResponse.cs b/Apps.Systran/Models/Response/ListEntriesResponse.cs new file mode 100644 index 0000000..2780384 --- /dev/null +++ b/Apps.Systran/Models/Response/ListEntriesResponse.cs @@ -0,0 +1,19 @@ +namespace Apps.Systran.Models.Response +{ + public class ListEntriesResponse + { + public IEnumerable Entries { get; set; } + public int TotalNoLimit { get; set; } + } + public class EntryInfo + { + public string Source { get; set; } + public string Target { get; set; } + public string PartOfSpeech { get; set; } + public string SourceLang { get; set; } + public string TargetLang { get; set; } + public string SourceId { get; set; } + public string TargetId { get; set; } + } + +} diff --git a/Apps.Systran/Models/Response/ProfilesListResponse.cs b/Apps.Systran/Models/Response/ProfilesListResponse.cs new file mode 100644 index 0000000..b9e0b4e --- /dev/null +++ b/Apps.Systran/Models/Response/ProfilesListResponse.cs @@ -0,0 +1,21 @@ +namespace Apps.Systran.Models.Response +{ + public class ProfilesListResponse + { + public int Total { get; set; } + public int Offset { get; set; } + public IEnumerable Profiles { get; set; } + } + + public class Profile + { + public string Id { get; set; } + public string Name { get; set; } + public bool Activated { get; set; } + public bool Running { get; set; } + public string Source { get; set; } + public string Target { get; set; } + public string SharingStatus { get; set; } + public string Owner { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/TranslateFileAsyncResponse.cs b/Apps.Systran/Models/Response/TranslateFileAsyncResponse.cs new file mode 100644 index 0000000..bac54b4 --- /dev/null +++ b/Apps.Systran/Models/Response/TranslateFileAsyncResponse.cs @@ -0,0 +1,10 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Systran.Models.Response +{ + public class TranslateFileAsyncResponse + { + [Display("Request ID")] + public string RequestId { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/TranslateTextResponse.cs b/Apps.Systran/Models/Response/TranslateTextResponse.cs new file mode 100644 index 0000000..03414d1 --- /dev/null +++ b/Apps.Systran/Models/Response/TranslateTextResponse.cs @@ -0,0 +1,44 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Systran.Models.Response +{ + public class TranslateTextResponse + { + [Display("Request ID")] + public string RequestId { get; set; } = string.Empty; + + [Display("Outputs")] + public List Outputs { get; set; } = new(); + } + + public class TranslationOutput + { + [Display("Info")] + public TranslationInfo? Info { get; set; } + + [Display("Translated text")] + public string Output { get; set; } = string.Empty; + + [Display("Back translation")] + public string? BackTranslation { get; set; } + + [Display("Source text")] + public string? Source { get; set; } + } + + public class TranslationInfo + { + [Display("Language identification")] + public LanguageIdentification? Lid { get; set; } + + } + + public class LanguageIdentification + { + [Display("Language")] + public string Language { get; set; } = string.Empty; + + [Display("Confidence level")] + public float Confidence { get; set; } + } +} diff --git a/Apps.Systran/Models/Response/UpdateDictionaryResponse.cs b/Apps.Systran/Models/Response/UpdateDictionaryResponse.cs new file mode 100644 index 0000000..6492b2d --- /dev/null +++ b/Apps.Systran/Models/Response/UpdateDictionaryResponse.cs @@ -0,0 +1,13 @@ +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Systran.Models.Response +{ + public class UpdateDictionaryResponse + { + [Display("Success")] + public bool Success { get; set; } + + [Display("Message")] + public string Message { get; set; } + } +} diff --git a/Apps.Systran/Models/TranslateLanguagesOptions.cs b/Apps.Systran/Models/TranslateLanguagesOptions.cs new file mode 100644 index 0000000..22fffcc --- /dev/null +++ b/Apps.Systran/Models/TranslateLanguagesOptions.cs @@ -0,0 +1,17 @@ +using Apps.Systran.DataSourceHandlers.EnumDataHandlers; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Dictionaries; + +namespace Apps.Systran.Models +{ + public class TranslateLanguagesOptions + { + [Display("Source language code")] + [StaticDataSource(typeof(LanguageCodeDataHandler))] + public string Source { get; set; } + + [Display("Target language code")] + [StaticDataSource(typeof(LanguageCodeDataHandler))] + public string Target { get; set; } + } +} diff --git a/Apps.Systran/Polling/Models/TranslateFileMemory.cs b/Apps.Systran/Polling/Models/TranslateFileMemory.cs new file mode 100644 index 0000000..c8d6e0e --- /dev/null +++ b/Apps.Systran/Polling/Models/TranslateFileMemory.cs @@ -0,0 +1,9 @@ +namespace Apps.Systran.Polling.Models +{ + public class TranslateFileMemory + { + public DateTime? LastPollingTime { get; set; } + + public bool Triggered { get; set; } + } +} diff --git a/Apps.Systran/Polling/Models/TranslationResultResponse.cs b/Apps.Systran/Polling/Models/TranslationResultResponse.cs new file mode 100644 index 0000000..7d06773 --- /dev/null +++ b/Apps.Systran/Polling/Models/TranslationResultResponse.cs @@ -0,0 +1,12 @@ +using Blackbird.Applications.Sdk.Common.Files; + +namespace Apps.Systran.Polling.Models +{ + public class TranslationResultResponse + { + public string RequestId { get; set; } + public string Status { get; set; } + public DateTime FinishedAt { get; set; } + public FileReference File { get; set; } + } +} diff --git a/Apps.Systran/Polling/Models/TranslationStatusResponse.cs b/Apps.Systran/Polling/Models/TranslationStatusResponse.cs new file mode 100644 index 0000000..a8347c4 --- /dev/null +++ b/Apps.Systran/Polling/Models/TranslationStatusResponse.cs @@ -0,0 +1,29 @@ + + +using Blackbird.Applications.Sdk.Common; + +namespace Apps.Systran.Polling.Models +{ + public class TranslationStatusResponse + { + [Display("Request ID")] + public string RequestId { get; set; } + + [Display("Request status")] + public string Status { get; set; } + + public long? FinishedAtEpoch { get; set; } + public DateTime? FinishedAt + { + get + { + if (!FinishedAtEpoch.HasValue) + return null; + + return DateTimeOffset + .FromUnixTimeMilliseconds(FinishedAtEpoch.Value) + .UtcDateTime; + } + } + } +} diff --git a/Apps.Systran/Polling/TranslateFilePollingList.cs b/Apps.Systran/Polling/TranslateFilePollingList.cs new file mode 100644 index 0000000..6f1c560 --- /dev/null +++ b/Apps.Systran/Polling/TranslateFilePollingList.cs @@ -0,0 +1,90 @@ +using Apps.App.Api; +using Apps.Systran.Actions.Base; +using Apps.Systran.Polling.Models; +using Blackbird.Applications.Sdk.Common; +using Blackbird.Applications.Sdk.Common.Exceptions; +using Blackbird.Applications.Sdk.Common.Invocation; +using Blackbird.Applications.Sdk.Common.Polling; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; +using RestSharp; + +namespace Apps.Systran.Polling +{ + [PollingEventList] + public class TranslationPollingList(InvocationContext invocationContext, IFileManagementClient fileManagementClient) : BaseActions(invocationContext, fileManagementClient) + { + + [PollingEvent("On translation finished", "Triggered when the translation status is finished")] + public async Task> OnTranslationFinished( + PollingEventRequest request, + [PollingEventParameter][Display("Request ID")] string requestId) + { + if (request.Memory is null) + { + return new() + { + FlyBird = false, + Memory = new() + { + LastPollingTime = DateTime.UtcNow, + Triggered = false + } + }; + } + + var statusRequest = new SystranRequest($"/translation/file/status", Method.Get); + statusRequest.AddQueryParameter("requestId", requestId); + var statusResponse = await Client.ExecuteAsync(statusRequest); + + if (statusResponse.Data == null || + !statusResponse.Data.Status.Equals("finished", StringComparison.OrdinalIgnoreCase)) + { + return new() + { + FlyBird = false, + Memory = new() + { + LastPollingTime = DateTime.UtcNow, + Triggered = false + } + }; + } + + + var resultRequest = new SystranRequest($"/translation/file/result", Method.Get); + resultRequest.AddQueryParameter("requestId", requestId); + var resultResponse = await Client.ExecuteAsync(resultRequest); + + var rawResponse = await Client.ExecuteAsync(resultRequest); + + if (!rawResponse.IsSuccessful || rawResponse.RawBytes == null) + { + throw new PluginApplicationException($"Failed to retrieve the translation result. Status: {rawResponse.StatusCode}, Error: {rawResponse.ErrorMessage}"); + } + + var translatedFile = await FileManagementClient.UploadAsync( + new MemoryStream(rawResponse.RawBytes), + rawResponse.ContentType, + $"{requestId}"); + + + return new() + { + FlyBird = true, + Result = new TranslationResultResponse + { + RequestId = requestId, + Status = "finished", + FinishedAt = DateTime.UtcNow, + File = translatedFile + }, + Memory = new() + { + LastPollingTime = DateTime.UtcNow, + Triggered = true + } + }; + } + } + +} diff --git a/Apps.Systran/README.md b/Apps.Systran/README.md new file mode 100644 index 0000000..520ac5c --- /dev/null +++ b/Apps.Systran/README.md @@ -0,0 +1,60 @@ +# Blackbird.io Systran + +Blackbird is the new automation backbone for the language technology industry. Blackbird provides enterprise-scale automation and orchestration with a simple no-code/low-code platform. Blackbird enables ambitious organizations to identify, vet and automate as many processes as possible. Not just localization workflows, but any business and IT process. This repository represents an application that is deployable on Blackbird and usable inside the workflow editor. + +## Introduction + + + +Systran Translate API is built around a RESTful API and can be used in all types of applications. +This API Reference is intended for developers who want to write applications that can interact with the SYSTRAN Translate API. +You can integrate our translation technology directly in your internal or external applications to make them multilingual or translate texts and files by using the SYSTRAN Translate API. + +## Before setting + +Before you can connect you need to make sure that: +- You have a Systran account and [Set up your account](https://spns-dev-web.systran.us/en/authentication) +- You have created the API key in `my account -> setting -> API keys` + +## Connecting +1.Navigate to apps and search for Systran +2. Click _Add Connection_. +3. Name your connection for future reference e.g. 'My Systran connection'. +4. Fill in your API key obtained earlier. +5. Fill in your Instance URL. +6. Click _Connect_. + + +## Actions + +### Corpus + +- **Export corpus** Export a corpus file in `.tmx` format by its ID +- **Import corpus from TMX file** Add a new corpus from an `.tmx` file. + +### Dictionary + +- **Create dictionary** Create a dictionary and populate it using a `.tbx` file +- **Export dictionary** Export dictionary as TBX file +- **Update dictionary from TBX file** Update an existing dictionary by importing entries from a `.tbx` file + +### File + +- **Translate file** Translate a file from source language to target language +- **Translate file (Async)** Translate a file from source language to target language +- **Download translated file** Download a translated file by request ID + +### Text + +- **Translate text** Translate text + + +## Events + +- **On translation finished** Triggered when the translation status is finished + +## Feedback + +Do you want to use this app or do you have feedback on our implementation? Reach out to us using the [established channels](https://www.blackbird.io/) or create an issue. + + diff --git a/Apps.Systran/image/icon.png b/Apps.Systran/image/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..49a7ce7c6d40a2057239f90bca272a906da63b6a GIT binary patch literal 4281 zcma)8XH-+^)(!%C6;#5|5&5KwL1IEcrOrqVAuuHLmJtwXO7B8uL=@>g6afVVsgY0w z5s*<7h=4RBAs~c~5D0`Ml!Om;?p^oa_wBRJdCq$GyZ5`FXYaMnIrwrg4G=Ojh8qKT zcmM#N!wYaQ0k{V^aqM3}psj=U^}h?7Km}!;c3HqVaR-$NCT7 zkGG#r80_E;0QfJ!3}A+rM-FfV$ioZdId}(Ug@?$i?k@B(;_ z9682+l>Z1n|Mwj{M|k;w=OFw?g@3cU{+C=}#*_MApx01cL&pr({CvFs9DSG@$R~Ub@|#u0lV6~LL;TnN zn%NM!Uf(4uM?W9~1bGij1@Zy``T*u1|AhSCP&Qno5{F1zQ=fYjJW0Oe%{H%t?Wcb= z6nH09WbRfxg~-bps4Kq_)$noGpPiLin4+K>+#S+8TX`0!U6GdZ9m_~txWbpCw+&Z8|*@=J*7Kpv+(Tgm!b+Rtu~868S>FjxLVDr zfe5@`A=nW;A{kchGScVbpSEB~bP46;8fF+ntER}0WVh8Ou=&v)K^?(D^*ILs`<)f& zu5QQu)bsVKpDMddO=wg*xBM;gvy3A*+_$e(cIRr#jn~%3 z*3(>@A<@5^4ENWoSy~)@ham2iL{}#w2U%BGLB~wlobC_9b;o`XeV9vcP~BjB<^6PD zt!fsFZtpI*Kw zXaF<(!C29$_N7OI^|i3=Uje87{I5pv4qy{v{A=u|N{~nwlG_$yyl)~AH#_<5%_Eh* z+dXy9Oh*vvUyb%~uVzRTN4?K7`ZqP71yppfU+Ay+7Gce{y*6FbmvzWBX=>3mxd#AI zJ>gE(I@Ifonrgl7!pf1~zw};hr8yTU6MJ{rzR3CQ*eevnCp8Yv@c@Z z=Xkbmk`OT>-vVK8aGvTHK%go4Ziv^5n2w!H3PAD+A^FXRga@R9Sh*w41(zN5GHh0- z`r1l@IVmE)z>5P>lrgj0+A^jZS~Bh>o>N8p_IvAgU}C7@XTPy4_j9?bVSeX(sJvmryJ>@YtTsjzCp^`qiyOT!1oWi|bmur0t9B%1&eH-#<$%LJuXr2x z?8#2t6Z1>`VHOFaCOWqJ^I;^8c6xmi9NPFxS6nxmRbH`|g+bXXWb{JqmERkkF9!OV z=Sbd^ye+^vK2~c{?UT3ajvc67kfImwGG-RSb;U>fH7^$1Ijn_~>W}Xz?lv2j%N6NL zlQI=81yEH(-u>K|&fvSl)34JSQZIbltgOPy$CR!N5O%2cq?A#7=Hz~w!Q1jFeb=_Z9`kc4)cd)%fcON`_ z&Sn}vpI*Y_+E_6C4K9VS@4lZ$y5=|^#Mvu*%y%bpOMBPRxve3;_LRfoNxUSvQq8Lj8d*h=wh+j-faB|6}c zRQdJ-DFE<4_CNc^6OV#7GvS&ObD0Z+s^65&bS-@FZ(0o5X7lvLl`xl}Wm&>oW)SCf zr;0OIr$f1rBo<|_Wt*13UP_auiZ=9HIbFoy`8kYBd8+SZ_$hRk3F}Ot{ z)zLEOqDHHm_i7B_jMl%$_z=(fe&(Rtkr&zSWZ{kRz#W>G8=~l9OwG9TI?-~Wk3Gk7 zKl1{SsdpVd5L8H=V6Gxk1tO-CL~xKU=ac}$yy?~?$+Ie&WEVXOM*sAYhuTtZX|Ha9 zWA5k&y9-@UML?7yq3p8NE>(AEf^xo!Uk5}!54s1nPJ626kR#M0Uvr~OES0i=%IP}U z6*YHvy5I)t;kCKH)vaU5zQlsq_`=MlUZ2rtOD-dfryXK#;4L+E?#bj~-;fc@_COPL zubl@r5mTG&aN;cZQ}XkQdJo^G1+DboheEP%%&>enI}QNB>)>qINCQko1@stjCFCb} z-rlI2;_m2NOqe5Ele_~nN=2?aGh$Lq$j??6idfuAxW)#2x4_SfIis_Y+*-D%D428O zERr!9rrfRg5%pVQ_;hPwiqfe;aaKn>I!ps?r#$?}D*X;Oe%~uj(o>KSuAyL=B39pv z5xz(@f`Sv3A1XkyJ*lW|2$dfC($6Pt-xX6MLsh_Omn*LdOb zDBA8`wF-?n_-a!2Z_9r+s5dk-Ef z6~kTdeQVoC2)RUkv10jd#V6#8NMxrl6WW*y!NIfN)h9RcJNq>VR_2y|hEB;9YdXH~ z^YD6jOV<2cem3sU@5jmYm>FagnJC!y*!0==^tDQcm~Vl`9f@LnOk#!sXdELyku*<5 z7L3+TjLVzUj%m;IMdQ*6RV}*JbCMev_lQh%bnHbYTV~Mim6gwns{MdH%{CS-wt8FV zgZ$-5^Upd_@!ES=#XWH)!EBDH_5Fy>2~>^VxxcGAhDRn^m!^4-ul*?GQl)i)H;fHq zmnXkD1{v0spzDp&=$&?FEr}m1Q;L$D4nKQ_v8t~1lgoQj*By6FiKzIdGy{xayJ+)ILX;Q5NsnAy$yW;NzXv_}{Mv zuR+^~m^F7-3Fqs~Qw0*1sL>DJ;?`6 z&*q*_$Cn)dZtD|b6JA968!TtN0`NZg^}GB40B9woJ|h~D7OHkqs%BC)usb@K+KP|5 z{P>Y4^_umpd{LrOp=N$#{T;_yYK#6Rx_5}t@y+_R>`})mCDd}S-7_B^LuRxJ14icZG4`xV9Ei%*8kSDT$P z$0Y%g4?g|*gL0bMUI4%Xe(Nc_&f|WGMr(9MgUcK_HXHXhvw*%2W`gL#VK>$M{^UQ@ zh*#$2fJ||n1Du%eK@+p* zKo#2{I+68`d(XcnA9wYGYj(*f;Yxzu&0yzc!rTRq9Zf}#NuKQQ zgT3FL%bC)PAF4d=H(&a{UV6U>R%HBs*jo$D=SUBMa4RBsGNN5rQ^D%fHjaYuupbIX?4s?I> ziLEFal99Sj*Xo3!S2~-6-+3_#mR+EWCBg>)MhOLvt8u)#Sd3v-;Y)R>2{mo|_ojx7 zg7AuOXrV5lBbJm$xqB_=aVzZoR4j&6gxu%`#z(wl=Fp?T8G{}mvi5F4=IqEO=VMNw zsH}8UET)Cz8?;#W*%_DFR&<%bQbT;IFl;n3K|^$1dTQd%X&c%ml?GI&S7wOc60Jj6 zl?zsQ)!GwDd=|$G+~sRmkeqg%NZFUEHsw1M|AuW#|3qDRgQwwU@^{x`hF@E1a?gas zn!oC&Vs4%Vt$m#-{@km?jeXGK#b`(jb9S1w`D#9SV*RZhB}E6CP06LeFS+IAK$}s` zhL0r&;UKS{bAH$bp0lO}7T(I>IP-KsPz>bLwlqaKu)i);@9QYMr&blYR8)4-r=ZO4I1`|ahg0Rnb7Ny z1H0B!Q=B0x$0j@!trT4os}*vDb>%N4W+yEFLY8~#l>{Z6FbsLP3`AWtpK+bLJJ*Ur zK3=NXtzP}(LnJ7sFsJbY%T8tcMrBr9I03eO%wWv5+((0fznNrS(o~F_qUTwz>+2!r z!gVoR8S1;wNGAt^8tBBNJC0*nw2#ZuZpl$m$b>6oIw|0SjOcOHDC-x4kqng(eZ%P? zs(rw<1ZPZ)P577|b07I9fBEEpbon`;fJ?1NzhUn! z^5eQ^=dEoETHj|;rnif1M+YTV=P|>38XI`Z@JeH3AhtDicAz|yy{T(2F~70m-$!0& z(d;h3S0g5YDBU=C z)pl!GSeYPu(%&^pd!MZ9A#QRicYRm+jG%b_%ZYW$>?`%>TF%k7KCc;VCA$$U?pXJ3 rb7%yNo@(u$pHOV2VvaX77sy79OU+maZB_$# -Documentation coming soon. +Systran Translate API is built around a RESTful API and can be used in all types of applications. +This API Reference is intended for developers who want to write applications that can interact with the SYSTRAN Translate API. +You can integrate our translation technology directly in your internal or external applications to make them multilingual or translate texts and files by using the SYSTRAN Translate API. + +## Before setting + +Before you can connect you need to make sure that: +- You have a Systran account and [Set up your account](https://spns-dev-web.systran.us/en/authentication) +- You have created the API key in `my account -> setting -> API keys` + +## Connecting +1.Navigate to apps and search for Systran +2. Click _Add Connection_. +3. Name your connection for future reference e.g. 'My Systran connection'. +4. Fill in your API key obtained earlier. +5. Fill in your Instance URL. +6. Click _Connect_. + + +## Actions + +### Corpus + +- **Export corpus** Export a corpus file in `.tmx` format by its ID +- **Import corpus from TMX file** Add a new corpus from an `.tmx` file. + +### Dictionary + +- **Create dictionary** Create a dictionary and populate it using a `.tbx` file +- **Export dictionary** Export dictionary as TBX file +- **Update dictionary from TBX file** Update an existing dictionary by importing entries from a `.tbx` file + +### File + +- **Translate file** Translate a file from source language to target language +- **Translate file (Async)** Translate a file from source language to target language +- **Download translated file** Download a translated file by request ID + +### Text + +- **Translate text** Translate text + + +## Events + +- **On translation finished** Triggered when the translation status is finished ## Feedback diff --git a/Tests.Systran/Base/FileManager.cs b/Tests.Systran/Base/FileManager.cs new file mode 100644 index 0000000..1170ca0 --- /dev/null +++ b/Tests.Systran/Base/FileManager.cs @@ -0,0 +1,64 @@ +using Blackbird.Applications.Sdk.Common.Files; +using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces; + +namespace SystranTests.Base +{ + public class FileManager : IFileManagementClient + { + private readonly string inputFolder; + private readonly string outputFolder; + + public FileManager() + { + var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; + var projectDirectory = Directory.GetParent(baseDirectory).Parent.Parent.Parent.FullName; + + + var testFilesPath = Path.Combine(projectDirectory, "TestFiles"); + inputFolder = Path.Combine(testFilesPath, "Input"); + outputFolder = Path.Combine(testFilesPath, "Output"); + + Directory.CreateDirectory(inputFolder); + Directory.CreateDirectory(outputFolder); + } + + + public Task DownloadAsync(FileReference reference) + { + var path = Path.Combine(inputFolder, reference.Name); + Assert.IsTrue(File.Exists(path), $"File not found at: {path}"); + var bytes = File.ReadAllBytes(path); + + var stream = new MemoryStream(bytes); + return Task.FromResult((Stream)stream); + } + + public Task UploadAsync(Stream stream, string contentType, string fileName) + { + var path = Path.Combine(outputFolder, fileName); + new FileInfo(path).Directory.Create(); + using (var fileStream = File.Create(path)) + { + stream.CopyTo(fileStream); + } + + return Task.FromResult(new FileReference() { Name = fileName, ContentType=contentType }); + } + + public async Task UploadTestFileAsync(string fileName, string contentType = "text/plain") + { + var testFilePath = Path.Combine(inputFolder, fileName); + Assert.IsTrue(File.Exists(testFilePath), $"Test file not found at: {testFilePath}"); + + using var fileStream = new FileStream(testFilePath, FileMode.Open, FileAccess.Read); + + var fileReference = new FileReference + { + Name = fileName, + ContentType = contentType + }; + + return fileReference; + } + } +} diff --git a/Tests.Systran/Base/TestBase.cs b/Tests.Systran/Base/TestBase.cs new file mode 100644 index 0000000..3270f8b --- /dev/null +++ b/Tests.Systran/Base/TestBase.cs @@ -0,0 +1,34 @@ +using Blackbird.Applications.Sdk.Common.Authentication; +using Blackbird.Applications.Sdk.Common.Invocation; +using Microsoft.Extensions.Configuration; + +namespace SystranTests.Base +{ + public class TestBase + { + public IEnumerable Creds { get; set; } + + public InvocationContext InvocationContext { get; set; } + + public FileManager FileManager { get; set; } + + public TestBase() + { + var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + Creds = config.GetSection("ConnectionDefinition").GetChildren() + .Select(x => new AuthenticationCredentialsProvider(x.Key, x.Value)).ToList(); + + + var relativePath = config.GetSection("TestFolder").Value; + var projectDirectory = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.Parent.FullName; + var folderLocation = Path.Combine(projectDirectory, relativePath); + + InvocationContext = new InvocationContext + { + AuthenticationCredentialsProviders = Creds, + }; + + FileManager = new FileManager(); + } + } +} diff --git a/Tests.Systran/CorpusTests.cs b/Tests.Systran/CorpusTests.cs new file mode 100644 index 0000000..fdf2e31 --- /dev/null +++ b/Tests.Systran/CorpusTests.cs @@ -0,0 +1,56 @@ +using Apps.Systran.Actions; +using Apps.Systran.Models.Request; +using SystranTests.Base; + +namespace Tests.Systran +{ + [TestClass] + public class CorpusTests : TestBase + { + [TestMethod] + public async Task ImportCorpus_ValidFile_ReturnsResponse() + { + // Arrange + var fileReference = await FileManager.UploadTestFileAsync("test.tmx", "application/x-tmx+xml"); + + + var parameters = new ImportCorpusParameters + { + Name = "YOUR_CORPUS_NAME", + InputFile = fileReference, + Tag = new[] { "tag1", "tag2" } + }; + + var actions = new CorpusActions(InvocationContext, FileManager); + + + // Act + var result = await actions.ImportCorpus(parameters); + + // Assert + Assert.IsNotNull(result, "Response is null."); + Assert.IsNotNull(result.Corpus, "Corpus data is null."); + Assert.IsFalse(string.IsNullOrEmpty(result.Corpus.Id), "Corpus ID is missing."); + + } + + [TestMethod] + public async Task ExportCorpus_ValidCorpusId_ReturnsFileReference() + { + var corpusId = "YOUR_CORPUS_ID"; + var expectedContentType = "application/x-tmx+xml"; + + var parameters = new ExportCorpusParameters + { + CorpusId = corpusId + }; + + var actions = new CorpusActions(InvocationContext, FileManager); + + var result = await actions.ExportCorpus(parameters); + + Assert.IsNotNull(result, "Response is null."); + Assert.IsNotNull(result.FileResponse, "FileResponse is null."); + } + } +} diff --git a/Tests.Systran/DataSources.cs b/Tests.Systran/DataSources.cs new file mode 100644 index 0000000..2a37d46 --- /dev/null +++ b/Tests.Systran/DataSources.cs @@ -0,0 +1,68 @@ +using Apps.Systran.DataSourceHandlers; +using Blackbird.Applications.Sdk.Common.Dynamic; +using SystranTests.Base; + +namespace Tests.Systran +{ + [TestClass] + public class DataSources : TestBase + { + [TestMethod] + public async Task DictionaryDataHandlerReturnsValues() + { + //Arrange + var handler = new DictionaryDataHandler(InvocationContext); + + //Act + var data = await handler.GetDataAsync(new DataSourceContext { SearchString = "" }, CancellationToken.None); + + //Assert + foreach (var item in data) + { + Console.WriteLine($"{item.Value}: {item.DisplayName}"); + } + + Assert.IsNotNull(data); + Assert.AreNotEqual(0, data.Count(), "No corpora were returned."); + } + + [TestMethod] + public async Task CorporaDataHandlerReturnsValues() + { + // Arrange + var handler = new CorporaDataHandler(InvocationContext); + + // Act + var data = await handler.GetDataAsync(new DataSourceContext { SearchString = "" }, CancellationToken.None); + + // Assert + foreach (var item in data) + { + Console.WriteLine($"{item.Value}: {item.DisplayName}"); + } + + Assert.IsNotNull(data, "Handler returned null."); + Assert.AreNotEqual(0, data.Count(), "No corpora were returned."); + } + + + [TestMethod] + public async Task GetProfilesReturnsExpectedResults() + { + // Arrange + var handler = new ProfilesDataHandler(InvocationContext); + + // Act + var profiles = await handler.GetDataAsync(new DataSourceContext { SearchString = "" }, CancellationToken.None); + + // Assert + Assert.IsNotNull(profiles, "Profiles list is null."); + Assert.IsTrue(profiles.Any(), "No profiles returned."); + foreach (var profile in profiles) + { + Console.WriteLine($"{profile.Value}: {profile.DisplayName}"); + } + } + + } +} diff --git a/Tests.Systran/DictionaryTests.cs b/Tests.Systran/DictionaryTests.cs new file mode 100644 index 0000000..c5296b9 --- /dev/null +++ b/Tests.Systran/DictionaryTests.cs @@ -0,0 +1,91 @@ +using Apps.Systran.Actions; +using Apps.Systran.Models; +using Apps.Systran.Models.Request; +using SystranTests.Base; + +namespace Tests.Systran +{ + [TestClass] + public class DictionaryTests : TestBase + { + [TestMethod] + public async Task CreateDictionary_ValidFile_ReturnsResponse() + { + // Arrange + var fileReference = await FileManager.UploadTestFileAsync("test.tbx", "application/x-tbx+xml"); + + var parameters = new CreateDictionaryParameters + { + Name = "YOUR_DICTIONARY_NAME", + SourcePos = "noun", + Comment = "Test comment", + Type = "UD", + TbxFile = fileReference + }; + + var options = new TranslateLanguagesOptions + { + Source = "en", + Target = "es" + }; + + var actions = new DictionaryActions(InvocationContext, FileManager); + + // Act + var result = await actions.CreateDictionary(parameters, options); + + // Assert + Assert.IsNotNull(result, "Response is null."); + Assert.IsNotNull(result.Added, "Added dictionary data is null."); + Assert.IsFalse(string.IsNullOrEmpty(result.Added.Id), "Dictionary ID is missing."); + } + + + [TestMethod] + public async Task ExportDictionaryToTbx_ValidDictionaryId_ReturnsTbxFile() + { + // Arrange + var parameters = new ExportDictionaryRequest + { + DictionaryId = "YOUR_DICTIONARY_ID", + }; + + var actions = new DictionaryActions(InvocationContext, FileManager); + + // Act + var result = await actions.ExportDictionary(parameters); + + // Assert + Assert.IsNotNull(result, "Response is null."); + Assert.IsNotNull(result.FileResponse, "TBX file is null."); + Assert.IsFalse(string.IsNullOrEmpty(result.FileResponse.Name), "TBX file name is empty."); + } + + [TestMethod] + public async Task UpdateDictionary_ValidTbxFile_UpdatesDictionary() + { + // Arrange + var parameters = new UpdateDictionaryRequest + { + DictionaryId = "YOUR_DICTIONARY_ID", + File = await FileManager.UploadTestFileAsync("test.tbx", "application/x-tbx+xml") + }; + + var options = new TranslateLanguagesOptions + { + Source = "en", + Target = "es" + }; + + var actions = new DictionaryActions(InvocationContext, FileManager); + + // Act + var result = await actions.UpdateDictionary(parameters, options); + + // Assert + Assert.IsNotNull(result, "Response is null."); + Assert.IsTrue(result.Success, "Update operation failed."); + Assert.IsFalse(string.IsNullOrEmpty(result.Message), "Response message is empty."); + } + } +} diff --git a/Tests.Systran/GlobalUsings.cs b/Tests.Systran/GlobalUsings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/Tests.Systran/GlobalUsings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Tests.Systran/PollingTests.cs b/Tests.Systran/PollingTests.cs new file mode 100644 index 0000000..fae0e9c --- /dev/null +++ b/Tests.Systran/PollingTests.cs @@ -0,0 +1,74 @@ +using Apps.Systran.Actions; +using Apps.Systran.Models; +using Apps.Systran.Models.Request; +using Apps.Systran.Polling; +using Apps.Systran.Polling.Models; +using Blackbird.Applications.Sdk.Common.Polling; +using SystranTests.Base; + +namespace Tests.Systran +{ + [TestClass] + public class PollingTests : TestBase + { + [TestMethod] + public async Task TranslateFileAsync_WithPolling_ReturnsTranslatedFile() + { + // Arrange + var fileReference = await FileManager.UploadTestFileAsync("Translate.txt", "text/plain"); + + var inputRequest = new TranslateFileRequest + { + Input = fileReference, + Profile = null + }; + + var inputOptions = new TranslateLanguagesOptions + { + Source = "en", + Target = "fr" + }; + + var actions = new TranslationPollingList(InvocationContext, FileManager); + + // Act + var translateAction = new TranslateFileActions(InvocationContext, FileManager); + var translateResponse = await translateAction.TranslateFileAsync(inputOptions, inputRequest); + + Assert.IsNotNull(translateResponse, "Translate response is null."); + Assert.IsFalse(string.IsNullOrEmpty(translateResponse.RequestId), "Request ID is null or empty."); + + var requestId = translateResponse.RequestId; + PollingEventResponse pollingResponse = null; + + for (int i = 0; i < 1; i++) + { + var pollingRequest = new PollingEventRequest + { + Memory = new TranslateFileMemory + { + LastPollingTime = DateTime.UtcNow, + Triggered = false + } + }; + + pollingResponse = await actions.OnTranslationFinished(pollingRequest, requestId); + + if (pollingResponse.FlyBird) + { + break; + } + + await Task.Delay(2000); + } + + // Assert + Assert.IsNotNull(pollingResponse, "Polling response is null."); + Assert.IsTrue(pollingResponse.FlyBird, "Polling did not finish successfully."); + Assert.IsNotNull(pollingResponse.Result, "Polling result is null."); + Assert.AreEqual("finished", pollingResponse.Result.Status, "Translation status is not 'finished'."); + Assert.IsNotNull(pollingResponse.Result.File, "Translated file is null."); + Assert.IsFalse(string.IsNullOrEmpty(pollingResponse.Result.File.Name), "Translated file name is null or empty."); + } + } +} diff --git a/Tests.Systran/TestFiles/Input/Translate.txt b/Tests.Systran/TestFiles/Input/Translate.txt new file mode 100644 index 0000000..d07efd5 --- /dev/null +++ b/Tests.Systran/TestFiles/Input/Translate.txt @@ -0,0 +1,13 @@ +Cleaning Everything in 30 Minutes + +1Clean your bathroom. Clear everything off of your sink, toilet and the ledge of your bathtub. Spray these areas down with your cleaning product of choice and then give each area a quick scrub. Return everything you moved back to your sink, toilet, and tub. Grab any dirty linens and throw them directly in the washing machine or laundry hamper.[1] +• Scrubbing Bubbles is a great all-purpose product for bathroom cleaning. +• Allocate ten minutes to clean your bathroom. +• If you have time, wipe down the bathroom mirrors as well. +2. + + +2Clean your bedroom. Concentrate on making your bed; once your bed is made, your whole room will look far tidier. Pick up loose clothes and shoes and return them to their rightful places. If you have dirty dishes lying around, return those to the kitchen.[2] [3] +• Allocate five minutes to clean your bedroom. +• Save time off your bed making by leaving the flat sheet untucked - hospital corners take a long time to construct and your comforter will cover them anyway,[4] +• Open windows as you go to eliminate odors and get a nice cross-breeze going. diff --git a/Tests.Systran/TestFiles/Input/test.tbx b/Tests.Systran/TestFiles/Input/test.tbx new file mode 100644 index 0000000..ec7c29b --- /dev/null +++ b/Tests.Systran/TestFiles/Input/test.tbx @@ -0,0 +1,1358 @@ + + + + + + Astronomy + + + + + + + + + initial mass function + U7BLQQX1x245YA88LCwgWXcH1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407396 + maksym.kharchenko@blackbird.io + 1706108407396 + + + + + función inicial de masa + LBF5zmYqMf823X4c0e1aB7522 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407400 + maksym.kharchenko@blackbird.io + 1706108407400 + + + + + + + Father + U7BLQQX1x245YA88LCwgWXcH1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407396 + maksym.kharchenko@blackbird.io + 1706108407396 + + + + + Padre + LBF5zmYqMf823X4c0e1aB7522 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407400 + maksym.kharchenko@blackbird.io + 1706108407400 + + + + + + + Mother + U7BLQQX1x245YA88LCwgWXcH2 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407396 + maksym.kharchenko@blackbird.io + 1706108407396 + + + + + Madre + LBF5zmYqMf823X4c0e1aB7522 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407400 + maksym.kharchenko@blackbird.io + 1706108407400 + + + + + + + white dwarf + 8xLYTjGxMec26s4Ens081aet3 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407444 + maksym.kharchenko@blackbird.io + 1706108407444 + + + + + enana blanca + C9Bvdt7X0O84M0jgsm2n7SfF3 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407450 + maksym.kharchenko@blackbird.io + 1706108407450 + + + + + + + UV radiation + ClFQfx59Moq2iH4Meh0m65Y13 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407365 + maksym.kharchenko@blackbird.io + 1706108407365 + + + + + radiación UV + KJ3214xo1OQ9Eii0ex0glOaI5 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407367 + maksym.kharchenko@blackbird.io + 1706108407367 + + + + + + + spectral type + JcnN9xpO00r9U0x0s71gtQWG8 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407435 + maksym.kharchenko@blackbird.io + 1706108407435 + + + + + tipo espectral + RJOz6qWL0Qr4QO8Mas1sesRu0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407441 + maksym.kharchenko@blackbird.io + 1706108407441 + + + + + + + red dwarf + MfyYV9VdwTT2pi48lj00jYLh0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407425 + maksym.kharchenko@blackbird.io + 1706108407425 + + + + + enana roja + P6QGfxyv04v5Oy8wH12vnHxj2 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407430 + maksym.kharchenko@blackbird.io + 1706108407430 + + + + + + + reflection nebula + CV6hiSwMMnY2IL48GPgyUx1A1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407499 + maksym.kharchenko@blackbird.io + 1706108407499 + + + + + nebulosa de reflexión + FqBgXQQ20k24uw8U0B1mYci70 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407500 + maksym.kharchenko@blackbird.io + 1706108407500 + + + + + + + gamma-ray burst + VJHa1lV9wD1844i0sZ6kkblC6 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407317 + maksym.kharchenko@blackbird.io + 1706108407317 + + + + + explosión de rayos gamma + fn8r10mS3iBagWjwuE22o1bm6 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407322 + maksym.kharchenko@blackbird.io + 1706108407322 + + + + + + + Schwarzschild radius + LPy0AZgdxvu4ey9MmS0iT1Xc4 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407522 + maksym.kharchenko@blackbird.io + 1706108407522 + + + + + radio de Schwarzschild + 7uoRjlF9wlU2Dl4I2H0UpwLJ0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407524 + maksym.kharchenko@blackbird.io + 1706108407524 + + + + + + + globular cluster + ovp4xJAaMDj2hs4c1NxTJXnK0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407326 + maksym.kharchenko@blackbird.io + 1706108407326 + + + + + cúmulo globular + p3TDfp070uy80Yz008o8NtyMd + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407329 + maksym.kharchenko@blackbird.io + 1706108407329 + + + + + + + circumstellar accretion disk + BEhtwWT4wFI2R18EoJxaiuzr1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407313 + maksym.kharchenko@blackbird.io + 1706108407313 + + + + + + + open cluster + TfEQomgRwwl2s44Qm0xhCw902 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407304 + maksym.kharchenko@blackbird.io + 1706108407304 + + + + + cúmulo abierto + tpLW9bnQ06i5CF80gq0umq616 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407310 + maksym.kharchenko@blackbird.io + 1706108407310 + + + + + + + triple-alpha process + vkdia0Wjwoe5i1hgo91CwvVx0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407559 + maksym.kharchenko@blackbird.io + 1706108407559 + + + + + proceso triple-alfa + Ge89VICqMAC2918M1G3R2LP83 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407561 + maksym.kharchenko@blackbird.io + 1706108407561 + + + + + + + singularity + 1RJz76DTwUf4ad9oyw0A5mV90 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407550 + maksym.kharchenko@blackbird.io + 1706108407550 + + + + + singularidad + FIJaqEKr0g34up9grZ1MCGFh3 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407552 + maksym.kharchenko@blackbird.io + 1706108407552 + + + + + + + nebula + zLL6Dh7J0qU5gW9EIs0U8Vxh0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407483 + maksym.kharchenko@blackbird.io + 1706108407483 + + + + + nebulosa + Tb6n6n7j0at4sa98cj003F413 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407490 + maksym.kharchenko@blackbird.io + 1706108407490 + + + + + + + gravitational collapse + tD6MqNafwxn27m4YEaMd1rkT1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407384 + maksym.kharchenko@blackbird.io + 1706108407384 + + + + + colapso gravitacional + SB7pQQe1wlZ4GU8o9X1yOHjf3 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407386 + maksym.kharchenko@blackbird.io + 1706108407386 + + + + + + + carbon-nitrogen cycle + qmSdjKHy0SE4c0jM0C0M30bgs + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407534 + maksym.kharchenko@blackbird.io + 1706108407534 + + + + + ciclo CNO + A0pejO9OxMl5G69MLfxZ7v351 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407536 + maksym.kharchenko@blackbird.io + 1706108407536 + + + + + + + magnetic field + i41D4dlv18Pb8Bi0Se4c97650 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407369 + maksym.kharchenko@blackbird.io + 1706108407369 + + + + + campo magnético + bdkXxslbwJ05y78oNNxwKCnN1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407371 + maksym.kharchenko@blackbird.io + 1706108407371 + + + + + + + binary star + pmOciUCOgcm2AE4M2tMhGGkj + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407388 + maksym.kharchenko@blackbird.io + 1706108407388 + + + + + estrella binaria + 8tqV6lvEwUB2Eh4ApG0e9SDq1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407394 + maksym.kharchenko@blackbird.io + 1706108407394 + + + + + + + emission nebula + lTfIDcxf0Qn5q78Eidx0XO1W4 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407470 + maksym.kharchenko@blackbird.io + 1706108407470 + + + + + nebulosa de emisión + 0PJ3vcDY1EKbIShgK80UQOC76 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407475 + maksym.kharchenko@blackbird.io + 1706108407475 + + + + + + + solar mass + t6Wk8N2pw4D2WK40KbxfPoVI1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407555 + maksym.kharchenko@blackbird.io + 1706108407555 + + + + + masa solar + 6D5V6tvpw8o2OU4Q0l02UnHT2 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407557 + maksym.kharchenko@blackbird.io + 1706108407557 + + + + + + + Galaxy + yZbkstnhwJs24f4wEGMW3E9v + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407356 + maksym.kharchenko@blackbird.io + 1706108407356 + + + + + Galaxia + 3IeQImfMwPM2QP4wk6MW8Zy5 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407359 + maksym.kharchenko@blackbird.io + 1706108407359 + + + + + + + main-sequence + A1T9RfRo0upaM1xwtV2CcUcE3 + adjective + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407331 + maksym.kharchenko@blackbird.io + 1706108407331 + + + + + de secuencia principal + 0tT6bldlwnt5Ct8M2Qwqpzyn1 + adjective + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407333 + maksym.kharchenko@blackbird.io + 1706108407333 + + + + + + + star + CYXiYdtM0ab5yS806f0Ii2Sz1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407361 + maksym.kharchenko@blackbird.io + 1706108407361 + + + + + estrella + psK9MmW00OFbMhjwDt2t4Uom1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407363 + maksym.kharchenko@blackbird.io + 1706108407363 + + + + + + + proton-proton chain reaction + ONYNiIO2wcQ2OV4ETHwwBjmO + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407530 + maksym.kharchenko@blackbird.io + 1706108407530 + + + + + cadena protón-protón + VftVP79vg3W22N4kw5gRErjt + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407531 + maksym.kharchenko@blackbird.io + 1706108407531 + + + + + + + supernova + TRrP8dgW0G34mh8EHUwTjliH0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407403 + maksym.kharchenko@blackbird.io + 1706108407403 + + + + + supernova + BXg9TmPNwFM2km4wuX0Q5elV1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407407 + maksym.kharchenko@blackbird.io + 1706108407407 + + + + + + + red giant + 5SW0KCpx00Ch8Uzwvc1gBgNm5 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407414 + maksym.kharchenko@blackbird.io + 1706108407414 + + + + + gigante roja + zVsHvqiago42Ux4wAWwSXzZX + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407420 + maksym.kharchenko@blackbird.io + 1706108407420 + + + + + + + star cluster + CiW4hdm10cJ90ew0oTf0qQ0W8 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407346 + maksym.kharchenko@blackbird.io + 1706108407346 + + + + + cúmulo estelar + Z7HWndnhMSZ2am4Ub11UV6Ae0 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407352 + maksym.kharchenko@blackbird.io + 1706108407352 + + + + + + + electron degeneracy + hsOXu3Dzwry2oo4Uvtw529e7 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407546 + maksym.kharchenko@blackbird.io + 1706108407546 + + + + + degeneración de electrones + qgFhxORgw6o2OL4ce0wRPoK60 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407548 + maksym.kharchenko@blackbird.io + 1706108407548 + + + + + + + planetary nebula + qT6uEcc40YO4oF8UbRwFl2t70 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407457 + maksym.kharchenko@blackbird.io + 1706108407457 + + + + + nebulosa planetaria + YSssG4ZigKJ2vC4cd9gxa47a + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407463 + maksym.kharchenko@blackbird.io + 1706108407463 + + + + + + + blue straggler + kNHpSodGgXq2fv48X7gk0PSE1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407374 + maksym.kharchenko@blackbird.io + 1706108407374 + + + + + estrella rezagada azul + 6iq617u4x8O4Qd8UeD0C1Pae4 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407379 + maksym.kharchenko@blackbird.io + 1706108407379 + + + + + + + Chandrasekhar limit + Zi3hKegYMPL2GG4kBxMLT6xm + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407513 + maksym.kharchenko@blackbird.io + 1706108407513 + + + + + límite de Chandrasekhar + zyVHRGS3Mq15W1j08Y1wX0Kon + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407516 + maksym.kharchenko@blackbird.io + 1706108407516 + + + + + + + dark nebula + kscy0qFc10GmMmy0Wn54zOxKd + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407493 + maksym.kharchenko@blackbird.io + 1706108407493 + + + + + nebulosa oscura + xVbvuN0p0iMbEehg6Y0U1LZ39 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407497 + maksym.kharchenko@blackbird.io + 1706108407497 + + + + + + + protostar + GvaljZ9Zwkh2hw40a51E1gBM1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407338 + maksym.kharchenko@blackbird.io + 1706108407338 + + + + + protoestrella + z1YySfFD1a0hwdwwUd6SgyAd4 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407344 + maksym.kharchenko@blackbird.io + 1706108407344 + + + + + + + degeneracy + DvXzAZxZ0M44sG90A91Im8ZQ3 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407538 + maksym.kharchenko@blackbird.io + 1706108407538 + + + + + degeneración + u4teqiPKw3o2jr4EuwgMu0AS1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407542 + maksym.kharchenko@blackbird.io + 1706108407542 + + + + + + + event horizon + 8oVzoKBygar2Vc48b11qiThd3 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407526 + maksym.kharchenko@blackbird.io + 1706108407526 + + + + + horizonte de sucesos + 1qDlMCC30yi8MDiMpZ1Ytq134 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407527 + maksym.kharchenko@blackbird.io + 1706108407527 + + + + + + + neutron star + eUCTWKLW0Mp5Qk8oHM1c1WyF6 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407508 + maksym.kharchenko@blackbird.io + 1706108407508 + + + + + estrella de neutrones + Z0it1DiX1mTi8wAw7P2UB4YZ1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407511 + maksym.kharchenko@blackbird.io + 1706108407511 + + + + + + + neutron degeneracy + YdRzUNMDw1K4Yt9EtcxqaMqA1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407518 + maksym.kharchenko@blackbird.io + 1706108407518 + + + + + degeneración de neutrones + 7bTVtUcX0419ozigDS2RCs9T1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407519 + maksym.kharchenko@blackbird.io + 1706108407519 + + + + + + + black dwarf + yBO7EEvzgIK2qQ489hgB2gZX + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407563 + maksym.kharchenko@blackbird.io + 1706108407563 + + + + + enana negra + uL268QBJgGt2oE4kbjg0LyGJ1 + noun + false + false + false + Approved + false + maksym.kharchenko@blackbird.io + 1706108407564 + maksym.kharchenko@blackbird.io + 1706108407564 + + + + + + \ No newline at end of file diff --git a/Tests.Systran/TestFiles/Input/test.tmx b/Tests.Systran/TestFiles/Input/test.tmx new file mode 100644 index 0000000..e4822de --- /dev/null +++ b/Tests.Systran/TestFiles/Input/test.tmx @@ -0,0 +1,2 @@ + +
HelloHaloCOdexCOdexSixsixnineneun diff --git a/Tests.Systran/Tests.Systran.csproj b/Tests.Systran/Tests.Systran.csproj new file mode 100644 index 0000000..9433502 --- /dev/null +++ b/Tests.Systran/Tests.Systran.csproj @@ -0,0 +1,48 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + diff --git a/Tests.Systran/TranslationTests.cs b/Tests.Systran/TranslationTests.cs new file mode 100644 index 0000000..4903fa5 --- /dev/null +++ b/Tests.Systran/TranslationTests.cs @@ -0,0 +1,94 @@ +using Apps.App.Actions; +using Apps.Systran.Actions; +using Apps.Systran.Models; +using Apps.Systran.Models.Request; +using SystranTests.Base; + +namespace Tests.Systran +{ + [TestClass] + public class TranslationTests : TestBase + { + [TestMethod] + public async Task TranslateTextReturnsValidResponse() + { + // Arrange + var inputOptions = new TranslateLanguagesOptions + { + Source = "en", + Target = "fr" + }; + + var inputRequest = new TranslateTextRequest + { + Input = "YOUR_INPUT_TEXT", + WithInfo = true + }; + + var actions = new TranslateTextActions(InvocationContext); + + // Act + var result = await actions.TranslateText(inputOptions, inputRequest); + + // Assert + Assert.IsNotNull(result, "Response is null."); + } + + + [TestMethod] + public async Task TranslateFile_ValidFile_ReturnsResponse() + { + // Arrange + var fileReference = await FileManager.UploadTestFileAsync("Translate.txt"); + + var inputRequest = new TranslateFileRequest + { + Input = fileReference, + Profile = null + }; + + var inputOptions = new TranslateLanguagesOptions + { + Source = "en", + Target = "fr" + }; + + var actions = new TranslateFileActions(InvocationContext, FileManager); + + // Act + var result = await actions.TranslateFile(inputOptions, inputRequest); + + // Assert + Assert.IsNotNull(result, "Response is null."); + Assert.IsNotNull(result.FileResponse, "Translated file is null."); + } + + [TestMethod] + public async Task TranslateFileAsync_ValidInput_ReturnsRequestId() + { + // Arrange + var fileReference = await FileManager.UploadTestFileAsync("Translate.txt"); + + var inputRequest = new TranslateFileRequest + { + Input = fileReference, + Profile = null + }; + + var inputOptions = new TranslateLanguagesOptions + { + Source = "en", + Target = "fr" + }; + + var actions = new TranslateFileActions(InvocationContext, FileManager); + // Act + var result = await actions.TranslateFileAsync(inputOptions, inputRequest); + + // Assert + Assert.IsNotNull(result, "Response is null."); + Assert.IsFalse(string.IsNullOrEmpty(result.RequestId), "Request ID is empty or null."); + } + + } +} diff --git a/Tests.Systran/Validator.cs b/Tests.Systran/Validator.cs new file mode 100644 index 0000000..efcbaa8 --- /dev/null +++ b/Tests.Systran/Validator.cs @@ -0,0 +1,29 @@ +using Apps.App.Connections; +using Blackbird.Applications.Sdk.Common.Authentication; +using SystranTests.Base; + +namespace Tests.Systran +{ + [TestClass] + public class Validator : TestBase + { + [TestMethod] + public async Task ValidatesCorrectConnection() + { + var validator = new ConnectionValidator(); + + var result = await validator.ValidateConnection(Creds, CancellationToken.None); + Assert.IsTrue(result.IsValid); + } + + [TestMethod] + public async Task DoesNotValidateIncorrectConnection() + { + var validator = new ConnectionValidator(); + + var newCreds = Creds.Select(x => new AuthenticationCredentialsProvider(x.KeyName, x.Value + "_incorrect")); + var result = await validator.ValidateConnection(newCreds, CancellationToken.None); + Assert.IsFalse(result.IsValid); + } + } +} \ No newline at end of file diff --git a/Tests.Systran/appsettings.json.example b/Tests.Systran/appsettings.json.example new file mode 100644 index 0000000..e38309d --- /dev/null +++ b/Tests.Systran/appsettings.json.example @@ -0,0 +1,7 @@ +{ + "ConnectionDefinition": { + "apiKey": "YOUR_API_KEY", + "url": "YOUR_URL" + }, + "TestFolder": "YOUR_TEST_FOLDER" +} \ No newline at end of file