diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 16ec3ca9..06731ae9 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -30,6 +30,7 @@
+
diff --git a/src/ResXManager.Tests/Model/TranslatorTests.cs b/src/ResXManager.Tests/Model/TranslatorTests.cs
new file mode 100644
index 00000000..780db7ae
--- /dev/null
+++ b/src/ResXManager.Tests/Model/TranslatorTests.cs
@@ -0,0 +1,42 @@
+namespace ResXManager.Tests.Model;
+
+using System;
+
+using Xunit;
+
+public class TranslatorTests
+{
+ [Fact]
+ public void GoogleLiteParsesFragmentedResponseCorrectly()
+ {
+ const string input = """
+ [[["Ei! ","Hey there! ",null,null,10],["Como tá indo? ","How's it going? ",null,null,10],["Isso é ótimo! ","That's great! ",null,null,10],["K, tchau","K, bye",null,null,3,null,null,[[]],[[["66379e56ded86dd057796dbeaebad517","en_pt_2023q1.md"]]]]],null,"en",null,null,null,null,[]]
+ """;
+
+ var result = Translators.GoogleTranslatorLite.ParseResponse(input);
+
+ Assert.Equal("Ei! Como tá indo? Isso é ótimo! K, tchau", result);
+ }
+
+ [Fact]
+ public void GoogleLiteParsesInvalidResponseCorrectly()
+ {
+ const string input = $$"""
+ {"x":[["Ei! "]]}
+ """;
+
+ var result = Translators.GoogleTranslatorLite.ParseResponse(input);
+
+ Assert.Equal("", result);
+ }
+
+ [Fact]
+ public void GoogleLiteThrowsOnBadJson()
+ {
+ const string input = $$"""
+ {"x":Ei!"]]}
+ """;
+
+ Assert.ThrowsAny(() => Translators.GoogleTranslatorLite.ParseResponse(input));
+ }
+}
diff --git a/src/ResXManager.Tests/ResXManager.Tests.csproj b/src/ResXManager.Tests/ResXManager.Tests.csproj
index c8b16b0d..1b55c98c 100644
--- a/src/ResXManager.Tests/ResXManager.Tests.csproj
+++ b/src/ResXManager.Tests/ResXManager.Tests.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/ResXManager.Translators/GoogleTranslatorLite.cs b/src/ResXManager.Translators/GoogleTranslatorLite.cs
index 16aae63e..40256e96 100644
--- a/src/ResXManager.Translators/GoogleTranslatorLite.cs
+++ b/src/ResXManager.Translators/GoogleTranslatorLite.cs
@@ -7,16 +7,14 @@
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Runtime.Serialization;
using System.Text;
-using System.Text.RegularExpressions;
+using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using ResXManager.Infrastructure;
-using TomsToolbox.Essentials;
using TomsToolbox.Wpf.Composition.AttributedModel;
[DataTemplate(typeof(GoogleTranslatorLite))]
@@ -25,15 +23,10 @@ public class GoogleTranslatorLiteConfiguration : Decorator
}
[Export(typeof(ITranslator)), Shared]
-public class GoogleTranslatorLite : TranslatorBase
+public class GoogleTranslatorLite() : TranslatorBase("GoogleLite", "Google Lite", _uri, null)
{
private static readonly Uri _uri = new("https://translate.google.com/");
- public GoogleTranslatorLite()
- : base("GoogleLite", "Google Lite", _uri, null)
- {
- }
-
protected override async Task Translate(ITranslationSession translationSession)
{
foreach (var languageGroup in translationSession.Items.GroupBy(item => item.TargetCulture))
@@ -43,35 +36,24 @@ protected override async Task Translate(ITranslationSession translationSession)
var targetCulture = languageGroup.Key.Culture ?? translationSession.NeutralResourcesLanguage;
- using var itemsEnumerator = languageGroup.GetEnumerator();
- while (true)
+ foreach (var sourceItem in languageGroup)
{
- var sourceItems = itemsEnumerator.Take(1);
- if (translationSession.IsCanceled || !sourceItems.Any())
+ if (translationSession.IsCanceled)
break;
var parameters = new List(30);
- // ReSharper disable once PossibleNullReferenceException
- parameters.AddRange(new[]
- {
- "client", "dict-chrome-ex",
+ parameters.AddRange(
+ [
+ "client", "gtx",
+ "dt", "t",
"sl", GoogleLangCode(translationSession.SourceLanguage),
"tl", GoogleLangCode(targetCulture),
- "q", RemoveKeyboardShortcutIndicators(sourceItems[0].Source)
- });
-
- // ReSharper disable once AssignNullToNotNullAttribute
- var response = await GetHttpResponse(
- "https://clients5.google.com/translate_a/t",
- parameters,
- translationSession.CancellationToken).ConfigureAwait(false);
+ "q", RemoveKeyboardShortcutIndicators(sourceItem.Source)
+ ]);
- await translationSession.MainThread.StartNew(() =>
- {
- Tuple tuple = new(sourceItems[0], new Translation { TranslatedText = response });
- tuple.Item1.Results.Add(new TranslationMatch(this, tuple.Item2.TranslatedText, Ranking));
- }).ConfigureAwait(false);
+ var response = await GetHttpResponse("https://translate.googleapis.com/translate_a/single", parameters, translationSession.CancellationToken).ConfigureAwait(false);
+ await translationSession.MainThread.StartNew(() => { sourceItem.Results.Add(new TranslationMatch(this, response, Ranking)); }).ConfigureAwait(false);
}
}
}
@@ -81,8 +63,9 @@ private static string GoogleLangCode(CultureInfo cultureInfo)
var iso1 = cultureInfo.TwoLetterISOLanguageName;
var name = cultureInfo.Name;
+ string[] twCultures = ["zh-hant", "zh-cht", "zh-hk", "zh-mo", "zh-tw"];
if (string.Equals(iso1, "zh", StringComparison.OrdinalIgnoreCase))
- return new[] { "zh-hant", "zh-cht", "zh-hk", "zh-mo", "zh-tw" }.Contains(name, StringComparer.OrdinalIgnoreCase) ? "zh-TW" : "zh-CN";
+ return twCultures.Contains(name, StringComparer.OrdinalIgnoreCase) ? "zh-TW" : "zh-CN";
if (string.Equals(name, "haw-us", StringComparison.OrdinalIgnoreCase))
return "haw";
@@ -99,17 +82,21 @@ private static async Task GetHttpResponse(string baseUrl, ICollection not available in .NET Framework
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- result = result.Substring(2, result.Length - 4);
- return Regex.Unescape(result);
+
+ return ParseResponse(result);
}
- [DataContract]
- private sealed class Translation
+ public static string ParseResponse(string result)
{
- [DataMember(Name = "translatedText")]
- public string? TranslatedText { get; set; }
+ var node = JsonNode.Parse(result);
+
+ if ((node is JsonArray level1) && (level1.FirstOrDefault() is JsonArray level2))
+ {
+ return string.Concat(level2.OfType().Select(item => item.FirstOrDefault()));
+ }
+
+ return string.Empty;
}
/// Builds the URL from a base, method name, and name/value paired parameters. All parameters are encoded.
@@ -122,12 +109,12 @@ private static string BuildUrl(string url, ICollection pairs)
if (pairs.Count % 2 != 0)
throw new ArgumentException("There must be an even number of strings supplied for parameters.");
+ if (pairs.Count <= 0)
+ return string.Empty;
+
var sb = new StringBuilder(url);
- if (pairs.Count > 0)
- {
- sb.Append('?');
- sb.Append(string.Join("&", pairs.Where((s, i) => i % 2 == 0).Zip(pairs.Where((s, i) => i % 2 == 1), Format)));
- }
+ sb.Append('?');
+ sb.Append(string.Join("&", pairs.Where((s, i) => i % 2 == 0).Zip(pairs.Where((s, i) => i % 2 == 1), Format)));
return sb.ToString();
static string Format(string? a, string? b)
@@ -135,4 +122,4 @@ static string Format(string? a, string? b)
return string.Concat(WebUtility.UrlEncode(a), "=", WebUtility.UrlEncode(b));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ResXManager.Translators/ResXManager.Translators.csproj b/src/ResXManager.Translators/ResXManager.Translators.csproj
index ad29e736..4d1e2e56 100644
--- a/src/ResXManager.Translators/ResXManager.Translators.csproj
+++ b/src/ResXManager.Translators/ResXManager.Translators.csproj
@@ -58,6 +58,7 @@
+