public async Task GetClientState()
        {
            _id++;

            // construct json content
            var builder = new StringBuìlder();

            builder.Append("{\"jsonrpc\":\"2.0\",\"method\":\"getClientState\",\"params\":{\"v\":\"20180814\"},\"id\":");
            builder.Append(_id);
            builder.Append("}");
            var content = builder.ToString();

            var stringContent = new StringContent(content);

            using var request = new HttpRequestMessage(HttpMethod.Get, HttpsTranslateStateSetup);
            AddHeaders(request, stringContent, false);

            using var response = await _client.SendAsync(request);

            response.ThrowIfBlocked();
            response.EnsureSuccessStatusCode();

            await response.Content.ReadAsStringAsync();
        }
        public async Task Translate(ITranslationContext context)
        {
            try
            {
                await _sem.WaitAsync();

                await EnsureSetupState();

                _translationCount++;
                _id++;

                // construct json content
                long r = (long)(DateTime.UtcNow - Epoch).TotalMilliseconds;
                long n = 1;

                var builder = new StringBuìlder();
                builder.Append("{\"jsonrpc\":\"2.0\",\"method\":\"LMT_handle_jobs\",\"params\":{\"jobs\":[");

                List <UntranslatedTextInfo> untranslatedTextInfos = new List <UntranslatedTextInfo>();
                foreach (var untranslatedTextInfo in context.UntranslatedTextInfos)
                {
                    List <TranslationPart> parts = NewlineSplitter
                                                   .Split(untranslatedTextInfo.UntranslatedText)
                                                   .Select(x => new TranslationPart {
                        Value = x, IsTranslatable = !NewlineSplitter.IsMatch(x)
                    })
                                                   .ToList();

                    var usableParts = parts
                                      .Where(x => x.IsTranslatable)
                                      .Select(x => x.Value)
                                      .ToArray();

                    for (int i = 0; i < usableParts.Length; i++)
                    {
                        var usablePart = usableParts[i];

                        builder.Append("{\"kind\":\"default\",\"preferred_num_beams\":1,\"raw_en_sentence\":\""); // yes.. "en" no matter what source language is used
                        builder.Append(JsonHelper.Escape(usablePart));

                        var addedContext = new HashSet <string>();
                        builder.Append("\",\"raw_en_context_before\":[");
                        bool addedAnyBefore = false;
                        foreach (var contextBefore in untranslatedTextInfo.ContextBefore)
                        {
                            if (!addedContext.Contains(contextBefore))
                            {
                                builder.Append("\"");
                                builder.Append(JsonHelper.Escape(contextBefore));
                                builder.Append("\"");
                                builder.Append(",");
                                addedAnyBefore = true;
                            }
                        }
                        for (int j = 0; j < i; j++)
                        {
                            if (!addedContext.Contains(usableParts[j]))
                            {
                                builder.Append("\"");
                                builder.Append(JsonHelper.Escape(usableParts[j]));
                                builder.Append("\"");
                                builder.Append(",");
                                addedAnyBefore = true;
                            }
                        }
                        if (addedAnyBefore)
                        {
                            builder.Remove(builder.Length - 1, 1);
                        }

                        builder.Append("],\"raw_en_context_after\":[");
                        bool addedAnyAfter = false;
                        for (int j = i + 1; j < usableParts.Length; j++)
                        {
                            if (!addedContext.Contains(usableParts[j]))
                            {
                                builder.Append("\"");
                                builder.Append(JsonHelper.Escape(usableParts[j]));
                                builder.Append("\"");
                                builder.Append(",");
                                addedAnyAfter = true;
                            }
                        }
                        foreach (var contextAfter in untranslatedTextInfo.ContextAfter)
                        {
                            if (!addedContext.Contains(contextAfter))
                            {
                                builder.Append("\"");
                                builder.Append(JsonHelper.Escape(contextAfter));
                                builder.Append("\"");
                                builder.Append(",");
                                addedAnyAfter = true;
                            }
                        }
                        if (addedAnyAfter)
                        {
                            builder.Remove(builder.Length - 1, 1);
                        }
                        //builder.Append("],\"quality\":\"fast\"},");
                        builder.Append("]},");

                        n += usablePart.Count(c => c == 'i');
                    }

                    untranslatedTextInfos.Add(new UntranslatedTextInfo {
                        TranslationParts = parts, UntranslatedText = untranslatedTextInfo.UntranslatedText
                    });
                }
                builder.Remove(builder.Length - 1, 1); // remove final ","

                var timestamp = r + (n - r % n);

                builder.Append("],\"lang\":{\"user_preferred_langs\":[\"");
                builder.Append(FixLanguage(context.DestinationLanguage).ToUpperInvariant());
                builder.Append("\",\"");
                builder.Append(FixLanguage(context.SourceLanguage).ToUpperInvariant());
                builder.Append("\"],\"source_lang_user_selected\":\"");
                builder.Append(FixLanguage(context.SourceLanguage).ToUpperInvariant());
                builder.Append("\",\"target_lang\":\"");
                builder.Append(FixLanguage(context.DestinationLanguage).ToUpperInvariant());
                builder.Append("\"},\"priority\":-1,\"timestamp\":");
                builder.Append(timestamp.ToString(CultureInfo.InvariantCulture));
                builder.Append("},\"id\":");
                builder.Append(_id);
                builder.Append("}");
                var content = builder.ToString();

                var stringContent = new StringContent(content);

                using var request = new HttpRequestMessage(HttpMethod.Post, HttpsServicePointTemplateUrl);
                AddHeaders(request, stringContent, true);


                // create request
                using var response = await _client.PostAsync(HttpsServicePointTemplateUrl, stringContent);

                response.ThrowIfBlocked();
                response.EnsureSuccessStatusCode();

                var str = await response.Content.ReadAsStringAsync();

                ExtractTranslation(str, untranslatedTextInfos, context);
            }
            catch (BlockedException)
            {
                Reset();

                throw;
            }
            finally
            {
                _sem.Release();
            }
        }