Skip to content

Commit

Permalink
Merge pull request #678 from tkefauver/patch-1
Browse files Browse the repository at this point in the history
Improved fix for Google Lite translator
  • Loading branch information
tom-englert authored Jan 9, 2025
2 parents 5387f61 + b93e1e9 commit a82418b
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 44 deletions.
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="[6.0.4]" />
<PackageVersion Include="System.ServiceModel.Primitives" Version="[4.10.2]" />
<PackageVersion Include="System.ServiceModel.Http" Version="[4.10.2]" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="Throttle.Fody" Version="1.7.0" />
<PackageVersion Include="TomsToolbox.Composition" Version="2.21.0" />
<PackageVersion Include="TomsToolbox.Composition.Ninject" Version="2.21.0" />
Expand Down
42 changes: 42 additions & 0 deletions src/ResXManager.Tests/Model/TranslatorTests.cs
Original file line number Diff line number Diff line change
@@ -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<Exception>(() => Translators.GoogleTranslatorLite.ParseResponse(input));
}
}
1 change: 1 addition & 0 deletions src/ResXManager.Tests/ResXManager.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<ItemGroup>
<ProjectReference Include="..\ResXManager.Model\ResXManager.Model.csproj" />
<ProjectReference Include="..\ResXManager.Translators\ResXManager.Translators.csproj" />
<ProjectReference Include="..\ResXManager.View\ResXManager.View.csproj" />
<ProjectReference Include="..\ResXManager\ResXManager.csproj" />
</ItemGroup>
Expand Down
75 changes: 31 additions & 44 deletions src/ResXManager.Translators/GoogleTranslatorLite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand All @@ -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))
Expand All @@ -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<string?>(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<ITranslationItem, Translation> 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);
}
}
}
Expand All @@ -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";
Expand All @@ -99,17 +82,21 @@ private static async Task<string> GetHttpResponse(string baseUrl, ICollection<st

response.EnsureSuccessStatusCode();

#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods => 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<JsonArray>().Select(item => item.FirstOrDefault()));
}

return string.Empty;
}

/// <summary>Builds the URL from a base, method name, and name/value paired parameters. All parameters are encoded.</summary>
Expand All @@ -122,17 +109,17 @@ private static string BuildUrl(string url, ICollection<string?> 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)
{
return string.Concat(WebUtility.UrlEncode(a), "=", WebUtility.UrlEncode(b));
}
}
}
}
1 change: 1 addition & 0 deletions src/ResXManager.Translators/ResXManager.Translators.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<PackageReference Include="System.ComponentModel.Annotations" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Security.Cryptography.Pkcs" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="Throttle.Fody" PrivateAssets="all" />
<PackageReference Include="System.ServiceModel.Primitives" />
<PackageReference Include="System.ServiceModel.Http" />
Expand Down

0 comments on commit a82418b

Please sign in to comment.