crowdin / crowdin-api-client-dotnet Goto Github PK
View Code? Open in Web Editor NEW.NET client library for Crowdin API
Home Page: https://www.nuget.org/packages/Crowdin.Api/
License: MIT License
.NET client library for Crowdin API
Home Page: https://www.nuget.org/packages/Crowdin.Api/
License: MIT License
Crowdin Rest API now supports an API method that allows downloading file preview.
New API method:
It should be added to this API client as well as the corresponding unit tests.
I tried to call API ExportProjectTranslation
with below code, but got response error.
DownloadLink exportedLink = await apiClient.Translations.ExportProjectTranslation("ProjectId",
new ExportProjectTranslationRequest()
{
TargetLanguageId = "en",
ExportApprovedOnly = true, // seems invalid paramter for current Crowdin API Service.
});
and error detail:
Url | https://@@@@.crowdin.com/api/v2/projects/4/translations/exports |
---|---|
Method | POST |
Status | Error |
Message | Bad Request |
Authorization Type | Personal Access Token |
Request details | { "targetLanguageId": "en", "exportApprovedOnly": true }
Errors | { "errors": [ { "error": { "key": "exportApprovedOnly", "errors": [ { "code": "notFound", "message": "Field 'exportApprovedOnly' Not Found" } ] } } ] }
Please check this issue.
Is this a regression?
No. StringBatchOperations wasn't available in 2.12
In which version?
2.14
Description
We're currently working on the C# Crowdin API’s StringBatchOperations and we faced the following issue:
{ "ClassName": "System.NullReferenceException", "Message": "Object reference not set to an instance of an object.", "StackTraceString": " at Crowdin.Api.Core.JsonParser.ParseResponseObject[TData](JToken token)\r\n at Crowdin.Api.Core.JsonParser.ParseResponseList[TData](JObject rootElement)\r\n at Crowdin.Api.SourceStrings.SourceStringsApiExecutor.StringBatchOperations(Int32 projectId, IEnumerable1 patches)\r\n", }
When debugging in the SourceStringsApiExecutor.cs
, we found that the http response from CrowdinAPI has the status 200 OK and is returning the following jsonObject:
{ "data": [ { ""data"": { ""id"": 468549, ""projectId"": 11, ""fileId"": 1533, ""branchId"": null, ""directoryId"": 1535, ""identifier"": ""bulk_edit_6"", ""text"": ""Batch operations with send patch request"", ""type"": ""text"", ""context"": ""edited context using patch request"", ""maxLength"": 0, ""isHidden"": false, ""isDuplicate"": false, ""masterStringId"": null, ""revision"": 58, ""hasPlurals"": false, ""isIcu"": false, ""labelIds"": [], ""createdAt"": ""2023-06-05T12:00:32+02:00"", ""updatedAt"": ""2023-06-05T18:38:26+02:00"" } } ] }
As you can see, the response is missing the pagination object (like mentioned in CrowdinAPI) which is probably why the line return _jsonParser.ParseResponseList<SourceString>(crowdinApiResult.JsonObject);
in SourceStringsApiExecutor.cs
fails to parse it and triggers the NullReferenceException.
Please provide a link to a minimal reproduction of the bug
A simple call of StringBatchOperations
.
Please provide the exception or error you saw
{ "ClassName": "System.NullReferenceException", "Message": "Object reference not set to an instance of an object.", "Data": null, "InnerException": null, "HelpURL": null, "StackTraceString": " at Crowdin.Api.Core.JsonParser.ParseResponseObject[TData](JToken token)\r\n at Crowdin.Api.Core.JsonParser.ParseResponseList[TData](JObject rootElement)\r\n at Crowdin.Api.SourceStrings.SourceStringsApiExecutor.StringBatchOperations(Int32 projectId, IEnumerable1 patches)\r\n", "RemoteStackTraceString": null, "RemoteStackIndex": 0, "ExceptionMethod": null, "HResult": -2147467261, "Source": "Crowdin.Api", "WatsonBuckets": null }
Crowdin added 3 new API endpoints that should be also added to the corresponding sections of this API Client.
New code should be covered by Unit tests in the same way as the rest of the code.
Hello,
When calling ListLanguageTranslations, parse failed about LanguageTranslations, it's an abstract class.
It can be patched in StringTranslationsApiExecutor.cs
public Task<ResponseList<T>> ListLanguageTranslations<T>( int projectId, string languageId, string? stringIds = null, string? labelIds = null, int? fileId = null, string? croql = null, bool? denormalizePlaceholders = null, int limit = 25, int offset = 0) where T : LanguageTranslations { return ListLanguageTranslations<T>(projectId, languageId, new LanguageTranslationsListParams(stringIds, labelIds, fileId, croql, denormalizePlaceholders, limit, offset)); }
public async Task<ResponseList<T>> ListLanguageTranslations<T>( int projectId, string languageId, LanguageTranslationsListParams @params) where T : LanguageTranslations { var url = $"/projects/{projectId}/languages/{languageId}/translations"; CrowdinApiResult result = await _apiClient.SendGetRequest(url, @params.ToQueryParams()); return _jsonParser.ParseResponseList<T>(result.JsonObject); }
Hello,
sometimes AddStorage fails due to the stream that is disposed before the end of the upload task.
I think the stream should not be disposed by the AddStorage, but by the creator of the stream.
Patch in CrowdinApiClient.cs : remove the using clause
public Task<CrowdinApiResult> UploadFile(string subUrl, string filename, Stream fileStream)
{
//using Stream stream = fileStream;
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = new StreamContent(fileStream),
RequestUri = new Uri(FormRequestUrl(subUrl)),
};
request.Headers.Add("Crowdin-API-FileName", filename);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return SendRequest(request);
}
then you could dispose the stream in caller (StorageApiExecutor.cs) like this :
[PublicAPI]
public async Task<StorageResource> AddStorage(Stream fileStream, string filename)
{
using var stream = fileStream;
CrowdinApiResult result = await _apiClient.UploadFile(BaseSubUrl, filename, stream);
return _jsonParser.ParseResponseObject<StorageResource>(result.JsonObject);
}
or my prefered method : let the creator of the stream dispose the stream, example :
using (var St = System.IO.File.OpenRead(Source_File))
{
var Storage = await API.Storage.AddStorage(St, CNT.source);
}
We've added new API methods to the "Source Files" section that would be great to reflect in this API Client.
Crowdin Rest API now supports API methods that allow to manage screenshot labels.
New API methods:
It should be added to this API client as well as the corresponding unit tests.
When calling Webhooks.AddWebhook, I'm getting the following error:
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.String[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
When I checked webhook via UI, the Webhook integration was there. The endpoint creates the webhook but cannot serialize the response. there are two properties which are headers and payload, those properties are tiring to cast to a string array but they are objects.
Related documentation is here
For reference, this is the response I got from the server:
{
"data": {
"id": 37,
"projectId": 15,
"name": "Backend-Integration",
"url": "https://test.com/apiservice/translation/file-translated",
"events": [
"file.translated"
],
"headers": {
"auth": "token"
},
"payload": {
"file.translated": {
"eventType": "{{event}}",
"projectId": "{{projectId}}",
"fileId": "{{fileId}}",
"targetLanguageId": "{{targetLanguageId}}"
}
},
"isActive": true,
"batchingEnabled": false,
"requestType": "POST",
"contentType": "application/json",
"createdAt": "2022-09-19T17:22:23+03:00",
"updatedAt": "2022-09-19T17:22:23+03:00"
}
}
this is my pr request if you interested
This class is an important part of the application, so it would be great to increase its coverage by adding more Unit tests.
In Webhook response model, the Headers
property is of type Dictionary
:
public class Webhook
{
//...
[JsonProperty("headers")]
public IDictionary<string, string> Headers { get; set; }
}
however in the request model AddWebhookRequest
, it is just of type object
:
public class AddWebhookRequest
{
//...
[JsonProperty("headers")]
public object? Headers { get; set; }
}
When calling TranslationsApiExecutor.ListProjectBuilds, I'm getting the following error:
Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Crowdin.Api.Translations.TranslationProjectBuild+AttributesHolder[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
When I look at the response from the server, it appears that Attributes are not an array, but just an single object containing the attributes (this would make sense since it's the TranslationProjectBuild that is a list of built translations). The API reference also looks incorrect compared to what I get back from the server.
What I'm worried about is that the server might respond with an array of attributes in some cases - which would make it hard to work as a C# deserialized object. I do have a pull request ready to go for this one if I can get confirmation it never returns an array.
For reference, this is the response I got from the server:
{
"data": [
{
"data": {
"id": 13,
"projectId": 12345,
"status": "finished",
"progress": 100,
"createdAt": "2022-03-31T18:31:28+00:00",
"updatedAt": "2022-03-31T18:45:15+00:00",
"finishedAt": "2022-03-31T18:45:15+00:00",
"attributes": {
"branchId": null,
"directoryId": null,
"targetLanguageIds": [],
"skipUntranslatedStrings": false,
"skipUntranslatedFiles": false,
"exportApprovedOnly": false
}
}
}
],
"pagination": {
"offset": 0,
"limit": 25
}
}
When the BundleExporter.CheckBundleExportStatus
returns a status of "in_progress" we run into the following JSON deserialization issue;
System.ArgumentException: Error occurred during deserialization from JSON String
at Crowdin.Api.Core.Converters.DescriptionEnumConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) in C:\Projects\crowdin-api-client-dotnet\src\Crowdin.Api\Core\Converters\DescriptionEnumConverter.cs:line 85
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at Crowdin.Api.Core.JsonParser.ParseResponseObject[TData](JObject rootElement) in C:\Projects\crowdin-api-client-dotnet\src\Crowdin.Api\Core\JsonParser.cs:line 53
at Crowdin.Api.Bundles.BundlesApiExecutor.CheckBundleExportStatus(Int32 projectId, Int32 bundleId, String exportId) in C:\Projects\crowdin-api-client-dotnet\src\Crowdin.Api\Bundles\BundlesApiExecutor.cs:line 133
at OSM.Translations.Api.Services.CrowdinService.ExportBundle(Bundle bundleDefinition) in C:\Projects\OSM.Translations\OSM.Translations.Api\Services\CrowdinService.cs:line 55
at OSM.Translations.Api.Services.CrowdinService.GetBundle(String bundleName) in C:\Projects\OSM.Translations\OSM.Translations.Api\Services\CrowdinService.cs:line 21
at OSM.Translations.Api.V1.TranslationController.GetCrowdinTranslationsBundleAsZip(String bundleName) in C:\Projects\OSM.Translations\OSM.Translations.Api\V1\TranslationController.cs:line 23
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: application/json
Host: localhost:59954
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
:method: GET
Accept-Encoding: gzip, deflate, br
Accept-Language: en,nl;q=0.9,nl-NL;q=0.8
Referer: https://localhost:59954/swagger/index.html
sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-site: same-origin
sec-fetch-mode: cors
sec-fetch-dest: empty
Expected: It should properly parse the status to BuildStatus.InProgress
.
Reason: The Enum definition has a nice Description
attribute above it which is read by a custom deserializer, but it is initialized with inProgress
instead of in_progress
(perhaps because it has been changed recently?)
Hello, there is no "Owner" user role in this enum. This leads to a deserialization exception when calling ListProjectMembers.
During my attempt to implement automated project webhooks management, I discovered that some of the events are missing in the file:
[PublicAPI]
public enum EventType
{
[Description("file.translated")] FileTranslated,
[Description("file.approved")] FileApproved,
[Description("project.translated")] ProjectTranslated,
[Description("project.approved")] ProjectApproved,
[Description("translation.updated")] TranslationUpdated,
[Description("string.added")] StringAdded,
[Description("string.updated")] StringUpdated,
[Description("string.deleted")] StringDeleted,
[Description("suggestion.added")] SuggestionAdded,
[Description("suggestion.updated")] SuggestionUpdated,
[Description("suggestion.deleted")] SuggestionDeleted,
[Description("suggestion.approved")] SuggestionApproved,
[Description("suggestion.disapproved")] SuggestionDisapproved
}
The missing ones are for instance: file.added
or file.updated
.
Is there a particular reason why they were not included in the client?
The package in version 2.13.0 contains no DLL files inside. It's literally empty.
The Distribution creation API has been changed. Now you can create a distribution with the following parameters:
bundleIds
- array of bundle idsThe following parameters have been deprecated:
format
exportPattern
labelIds
A Distribution response has also been changed. Now it contains the bundleIds
field. The format
, exportPattern
, and labelIds
fields are deprecated.
In addition, the fileIds
request parameter is now optional.
All the changes are described in the API documentation.
Is this a regression?
Yes
In which version?
2.12 --> workaround possible
2.14 --> not more workaround
Description
We’re currently working on the C# Crowdin API’s AddTag 2.14. version. Using the clientWrapper we faced this issue:
Unexpected character encountered while parsing value: {. Path 'errors[0].message', line 6, position 18.
I downgraded the version to 2.12, same error happen. On this version, sendPostRequest is accessible so was easier to debug it:
string subUrl = FormUrl_ScreenshotTags(projectId, screenshotId);
CrowdinApiResult crowdinApiResult = await _client.SendPostRequest(subUrl, requests);
_jsonParser.ParseResponseObject<Tag[]>(crowdinApiResult.JsonObject);
This code correspond to the AddTag inside ScreenshotsApiExecutor.cs
crowdinApiResult is well responding with a property JsonObject that contain correct information. But it fails on _jsonParser.ParseResponseObject, since JsonObject data is an array and ParseResponseObject expects an object.
Here is the exact error of it:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Crowdin.Api.Screenshots.Tag' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path '', line 1, position 1.
That issue could potentially be not only on AddTag, but everywhere where JsonObject is used and we expect an array json as response.
Worth to mention that: there is no test case for AddTag but only for ReplaceTag if i see well the Screenshot -> TagsApiTest.cs
Please provide a link to a minimal reproduction of the bug
Simply call AddTag as it should be called -->
public async Task<Tag> AddTags(int projectId, int screenshotId, IEnumerable<AddTagRequest> request)
{
AddTagRequest[] requests = new AddTagRequest[]
{
new AddTagRequest
{
StringId = 328525 , // Valeur de l'identifiant de chaîne
Position = new Position { X = 10, Y = 20, Height = 10, Width = 10 } // Valeurs de la position X et Y
},
// Ajoutez d'autres éléments AddTagRequest ici si nécessaire
};
return await _client.Screenshots.AddTag(projectId, screenshotId, request);
}
Please provide the exception or error you saw
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Crowdin.Api.Screenshots.Tag' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path '', line 1, position 1.
This class has no code coverage by Unit tests and it would be great to add tests.
Hi,
I saw that that projects Crowdin.Api.Samples.csproj
and Crowdin.Api.Tests.csproj
are still using .NET 5.0 version.
As support for .NET 5.0 version is already ended back in May, 2022. 🔗
I suggest these projects can be migrated to current .NET 6.0 version.
Continuation of #111 but for Glossaries
Question #111 (comment)
We've added a few new API endpoints to manage Bundles in Crowdin - https://developer.crowdin.com/api/v2/#tag/Bundles
It would be great to add the new methods to the corresponding class - BundlesApiExecutor.cs and include tests.
The CrowdinApiClient
class contains a very strict validation when it comes to the Content-Type
response header.
private static readonly MediaTypeHeaderValue DefaultContentType = MediaTypeHeaderValue.Parse("application/json");
// ...
if (response.Content.Headers.ContentType.Equals(DefaultContentType))
I faced an issue with this validation while setting up a mock API for integration tests in my system.
Most of the modern WebApis return the header in such a way Content-Type: application/json; charset=utf-8
.
This header value does not match the validation.
The solution seems pretty easy to develop. Easing the validation should not break any of the working parts of the client.
https://github.com/crowdin/crowdin-api-client-dotnet/search?q=eta
This property is being deprecated and should be removed. Actually, it never provided any usable result so probably no one is dependent on it.
Crowdin added new API endpoint - String Batch Operations.
It would be great to add the new method to this API client as well as the corresponding unit tests.
If project language access policy is "Open" the invite URL section will look like the following:
<invite_url>
<translator>
https://crowdin.com/project/test-project/invite
</translator>
<proofreader>
https://crowdin.com/project/test-project/invite?d=95l625i6r4v6p483v6q4r44303
</proofreader>
</invite_url>
But if project language access policy is "Moderate" (Limited access to the languages), invite_url
has the following structure:
<invite_url>
<item>
<language>Ukrainian</language>
<translator>
https://crowdin.com/project/test-project/invite?d=d5l6k4h685v6p483v6q4r44303
</translator>
<proofreader>
https://crowdin.com/project/test-project/invite?d=95l625i6r4v6p483v6q4r44303
</proofreader>
</item>
</invite_url>
In this case program crashes with error:
Unhandled exception. System.InvalidOperationException: There is an error in XML document (68, 7).
---> System.InvalidOperationException: There is an error in XML document (68, 7).
---> System.Xml.XmlException: Missed expected element 'translator' Line 68, position 7.
......
The problem is in ProjectInviteUrls.cs
file, ReadXml
method - it expects as required 'translator' and 'proofreader' nodes and does not expect item
nodes.
Crowdin Rest API now supports the API methods for TM (Translation Memory) segments management.
New API methods:
It should be added to this API client as well as the corresponding unit tests.
As far as I can tell, Crowdin.Api.TranslationStatus.TranslationStatusApiExecutor.GetLanguageProgress is the way to reference the Get Language Progress call listed in the API reference. However, the API reference requires a language ID in the form of a string (e.g. "en", "es", etc.), but here it is defined as an int which doesn't seem to have any correlation to what the API actually expects.
Am I missing something?
The coverage of this class is low and it would be great to fully cover it by writing Unit tests.
We've added a new API methods section - Organization-Webhooks. It's a webhooks that allow you to collect information about events that happen in your Crowdin account/organization.
It would be great to reflect these API changes in the API Client.
Note: the events
list is different for crowdin.com and Crowdin Enterprise APIs.
Current:
The call BundleApiExector.ExportBundle()
aka POST api/v2/projects/{projectId}/bundles/{bundleId}/exports
doesn't require any content, the package doesn't send any as expected, however when using it, the API now returns the following;
Crowdin.Api.CrowdinApiException: Unsupported Content-Type Header
at Crowdin.Api.CrowdinApiClient.CheckDefaultPreconditionsAndErrors(HttpResponseMessage response) in C:\Projects\crowdin-api-client-dotnet\src\Crowdin.Api\CrowdinApiClient.cs:line 416
at Crowdin.Api.CrowdinApiClient.SendRequest(Func`1 createRequestMessage) in C:\Projects\crowdin-api-client-dotnet\src\Crowdin.Api\CrowdinApiClient.cs:line 354
at Crowdin.Api.Bundles.BundlesApiExecutor.ExportBundle(Int32 projectId, Int32 bundleId) in C:\Projects\crowdin-api-client-dotnet\src\Crowdin.Api\Bundles\BundlesApiExecutor.cs:line 119
at OSM.Translations.Api.Services.CrowdinService.ExportBundle(Bundle bundleDefinition) in C:\Projects\OSM.Translations\OSM.Translations.Api\Services\CrowdinService.cs:line 50
at OSM.Translations.Api.Services.CrowdinService.GetBundleDownloadUrl(String bundleName) in C:\Projects\OSM.Translations\OSM.Translations.Api\Services\CrowdinService.cs:line 32
at OSM.Translations.Api.V1.TranslationController.GetCrowdinTranslationBundleAsDownloadableUrl(String bundleName) in C:\Projects\OSM.Translations\OSM.Translations.Api\V1\TranslationController.cs:line 31
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: application/json
Host: localhost:59954
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
:method: GET
Accept-Encoding: gzip, deflate, br
Accept-Language: en,nl;q=0.9,nl-NL;q=0.8
Referer: https://localhost:59954/swagger/index.html
sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-site: same-origin
sec-fetch-mode: cors
sec-fetch-dest: empty
Expected:
Either the API shouldn't expect the Content-Type
field on this call, or it should be set through the package (which would require creating JsonContent even though none is included).
API Reference: Add Task
The API response will also include this new field.
This is relevant for both the crowdin.com and Crowdin Enterprise APIs.
The new parameter should also be reflected in this API client.
This property was removed from the API:
https://github.com/crowdin/crowdin-api-client-dotnet/search?q=defaultProjectId
Also, the defaultProjectIds
property is missing, it should be added. https://developer.crowdin.com/api/v2/#operation/api.tms.get
It would be good to write some Unit tests for the classes of this client.
Write tests for most important parts of src/Crowdin.Api.
Crowdin has recently enhanced its response for the Progress API Methods:
For example:
{
"data": [
{
"data": {
"words": {
"total": 7249,
"translated": 3651,
+ "preTranslateAppliedTo": 1254,
"approved": 3637
},
"phrases": {
"total": 3041,
"translated": 2631,
+ "preTranslateAppliedTo": 1254,
"approved": 2622
},
"translationProgress": 86,
"approvalProgress": 86,
"languageId": "af"
}
}
],
"pagination": {
"offset": 0,
"limit": 25
}
}
The response of these methods now contains a new field preTranslateAppliedTo
in the words
and phrases
objects.
These new fields should also be reflected in this API Client.
HttpRequestMessage
instance is reused during retries which throws an exception.
HttpClient
lifetime is equal to the lifetime of the entire CrowdinApiClient
.
When retries mechanism is enabled
System.InvalidOperationException
Message=The request message was already sent. Cannot send the same request message multiple times.
Source=System.Net.Http
StackTrace:
at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
at System.Net.Http.HttpClient.CheckRequestBeforeSend(HttpRequestMessage request)
at Crowdin.Api.CrowdinApiClient.<>c__DisplayClass90_0.<SendRequest>b__0()
at Crowdin.Api.Core.Resilience.RetryService.<ExecuteRequestAsync>d__2`1.MoveNext()
at Crowdin.Api.Core.Resilience.RetryService.<ExecuteRequestAsync>d__2`1.MoveNext()
at Crowdin.Api.CrowdinApiClient.<SendRequest>d__90.MoveNext()
at Crowdin.Api.Languages.LanguagesApiExecutor.<ListSupportedLanguages>d__5.MoveNext()
at Crowdin.Api.CrowdinApiClient.<WithFetchAll>d__89`1.MoveNext()
at Program.<>c__DisplayClass0_0.<<<Main>$>g__GetSupportedLanguages|5>d.MoveNext()
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Crowdin.Api" Version="2.11.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>
</Project>
var credentials = new CrowdinCredentials
{
AccessToken = "secret",
Organization = "test-org",
BaseUrl = "http://localhost:58190" // some invalid Url on purpose, to make it fail
};
var client = new CrowdinApiClient(
credentials,
retryService: new RetryService(new RetryConfiguration
{
RetriesCount = 5,
WaitIntervalMilliseconds = 1500
})
);
var languages = await CrowdinApiClient
.WithFetchAll((limit, offset) => client.Languages.ListSupportedLanguages(limit, offset), amountPerRequest: 500);
The only workaround for this moment to make retries work is to define external policy and wrap the entire process of CrowdinApiClient
instance creation and sending a single request as one.
I've come up with a solution for this issue in this PR.
Creating a new instance of the HttpClient
and a new instance of the HttpRequestMessage
.
That solved the issue.
It's a breaking change due to changes in the constructor of the CrowdinApiClient
.
It also resolves another issue. The HttpClient
instance should be used only for a very small period of time. In the suggested solution the HttpClient
instance is created before every API call.
An additional benefit is that now the developers can use IHttpClientFactory
to create new instances of the HttpClient
.
This code can't be really unit tested. I only managed to test it locally with API calls.
The signature of the retry method can be improved.
From
public interface IRetryService
{
Task<T> ExecuteRequestAsync<T>(Func<Task<T>> func);
}
Into
public interface IRetryService
{
Task<HttpResponseMessage> ExecuteRequestAsync(Func<Task<HttpResponseMessage>> func);
}
This will allow more advanced users to define their own Retry services e.g. using Polly
thanks to the HttpResponseMessage
publicly exposed in the method's signature.
AddProjectRequest class is empty, although there are a lot of request parameters in the documentation for this request(https://developer.crowdin.com/api/v2/#operation/api.projects.post)
Crowdin Rest API now supports the qaChecksIgnorableCategories
field on the Projects API.
This field allows you to retrieve the list of QA Check categories that can be ignored for a Crowdin project.
Affected API methods:
It should be reflected in this API client and the corresponding unit tests should be updated.
We've added new possible import options for files:
Add File: new ImportOptions
possible value - Docx File Import Options (including the response)
Update or Restore File: new ImportOptions
possible value - Docx File Import Options (including the response)
Edit File: new /importOptions/*
path parameters
So these changes should be reflected in this API Client.
When TranslatableElements = "/strings//group/string[@txt]"
, elements matching /strings/group/string[@txt]
should be translatable in the Crowdin web interface, but are not. For instance, in an XML file containing
<?xml version="1.0" encoding="UTF-8"?>
<strings>
<group id="TopGroup1">
<string txt="No Records"/>
</group>
<group id="TopGroup2">
<group id="SubGroup1">
<string txt="Some Text"/>
</group>
<group id="SubGroup2">
<group id="SubSubGroup">
<string txt="Anywhere"/>
</group>
</group>
</group>
</strings>
all txt
attributes should be translatable, but only those under subgroups are (Some Text
, Anywhere
) and those under the top-level group are not (No Records
).
code snippet:
var httpClient = new HttpClient { BaseAddress = new Uri("https://api.crowdin.com/api/") };
var crowdinClient = new Client(httpClient);
var files = new Dictionary<string, FileInfo>
{
["sample.xml"] = new FileInfo("sample.xml")
};
var addFileParams = new AddFileParameters
{
Files = files,
TranslatableElements = new[] {"/strings//group/string[@txt]"}
};
await crowdinClient.AddFile(projectId, credentials, addFileParams);
Hi,
I tried to call the ExportProjectTranslation
API with the below code:
var downloadLink = await _crowdin.Translations.ExportProjectTranslation(projectId, new ExportProjectTranslationRequest()
{
TargetLanguageId = langId,
});
Note that, I want to get the original file format so I didn't pass Format
property (according to https://developer.crowdin.com/api/v2/#operation/api.projects.translations.exports.post):
Note: the format parameter is required in all cases except when you'd like to export translations for a single file in its original format
I also tried passing the format, but the TranslationFormat
enum does not have the "original" option.
Please help, thank you!
A Nuget package of the Crowdin.Api Project would be very useful.
I am building a tool based on the Crowdin.Api and it would be nice if the very good code that is sitting in develop was merged to master and a Nuget package published.
Crowdin Rest API Add File and Update or Restore File now support new ImportOptions
types:
These new Import Options should be added to this API client as well as the corresponding unit tests.
Hello,
calling EditTaskArchivedStatus raises an exception about mediatype.
Patch in CrowdinApiClient.cs
private HttpContent CreateJsonContent(object body, bool isPatch = false)
{
string bodyJson = JsonConvert.SerializeObject(body, DefaultJsonSerializerOptions);
MediaTypeHeaderValue contentType = isPatch
// ? MediaTypeHeaderValue.Parse("application/json-patch+json")
? MediaTypeHeaderValue.Parse("application/json")
: DefaultContentType;
return new StringContent(bodyJson, Encoding.UTF8, contentType.MediaType);
}
Listing webhooks for a project throws an exception
Unexpected character encountered while parsing value: {. Path 'payload['project.approved'].project', line 15, position 18.
at Newtonsoft.Json.JsonTextReader.ReadStringValue(ReadType readType)
at Newtonsoft.Json.JsonTextReader.ReadAsString()
at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateDictionary(IDictionary dictionary, JsonReader reader, JsonDictionaryContract contract, JsonProperty containerProperty, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at Crowdin.Api.Core.JsonParser.ParseResponseObject[TData](JObject rootElement) in JsonParser.cs:line 38
at System.Linq.Enumerable.SelectIListIterator`2.ToList()
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Crowdin.Api.Core.JsonParser.ParseResponseList[TData](JObject rootElement) in JsonParser.cs:line 41
at Crowdin.Api.Webhooks.WebhooksApiExecutor.<ListWebhooks>d__4.MoveNext() in WebhooksApiExecutor.cs:line 39
at Program.<<Main>$>d__0.MoveNext()
which is caused by an invalid type for the payload
property.
Given an example webhook JSON response (Crowdin's default payload):
[
{
"data": {
"id": 9,
"projectId": 7,
"name": "File proofreading complete",
"url": "https://something.com",
"events": ["project.approved"],
"headers": {
"x-api-key": "secret"
},
"payload": {
"project.approved": {
"event": "{{event}}",
"project": {
"id": "{{projectId}}",
"userId": "{{projectUserId}}",
"sourceLanguageId": "{{projectSourceLanguageId}}",
"targetLanguageIds": "{{projectTargetLanguageIds}}",
"identifier": "{{projectIdentifier}}",
"name": "{{projectName}}",
"createdAt": "{{projectCreatedAt}}",
"updatedAt": "{{projectUpdatedAt}}",
"lastActivity": "{{projectLastActivity}}",
"description": "{{projectDescription}}",
"url": "{{projectUrl}}",
"cname": "{{projectCname}}",
"logo": "{{projectLogo}}",
"isExternal": "{{projectIsExternal}}",
"externalType": "{{projectExternalType}}",
"hasCrowdsourcing": "{{projectHasCrowdsourcing}}"
},
"targetLanguage": {
"id": "{{targetLanguageId}}",
"name": "{{targetLanguageName}}",
"editorCode": "{{targetLanguageEditorCode}}",
"twoLettersCode": "{{targetLanguageTwoLettersCode}}",
"threeLettersCode": "{{targetLanguageThreeLettersCode}}",
"locale": "{{targetLanguageLocale}}",
"androidCode": "{{targetLanguageAndroidCode}}",
"osxCode": "{{targetLanguageOsxCode}}",
"osxLocale": "{{targetLanguageOsxLocale}}",
"textDirection": "{{targetLanguageTextDirection}}",
"dialectOf": "{{targetLanguageDialectOf}}"
}
}
},
"isActive": true,
"batchingEnabled": false,
"requestType": "POST",
"contentType": "application/json",
"createdAt": "2023-05-05T10:22:40+02:00",
"updatedAt": "2023-05-05T10:22:40+02:00"
}
}
]
and the CSharp model
public class Webhook
{
// ...
[JsonProperty("payload")]
public IDictionary<string, IDictionary<string, string>> Payload { get; set; }
}
as we can see, the property Payload
is of type IDictionary<string, IDictionary<string, string>>
. However, things get worse when we look at the JSON file property payload['project.approved'].project
. JsonConverter
expects it to be of type string
however there is another Dictionary
here.
The AddWebhook
request accepts the Payload
property just as object
. Maybe using object
type in the response would resolve that problem.
Or maybe in both cases, the Payload
property should be of type string, accepting any object already serialized to a JSON string?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.