Skip to content

Commit 8f03136

Browse files
authored
Implemented Translator for LibreTranslate (#739)
* Implemented Translator for LibreTranslate * LibreTranslate: Added option for number of alternatives The API offers the function to provide alternative translations, so it would make sense to offer this to the user as well. * Revert unrelated changes * Added missing pragma * Fixed build of unit test * Removed obsolete comment * Added "/translate" to the API url * Changed URI to point to the github repo
1 parent 12f2d20 commit 8f03136

4 files changed

Lines changed: 458 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
namespace ResXManager.Tests.Model;
2+
3+
using System;
4+
using System.Globalization;
5+
using System.Linq;
6+
using System.Net.Http;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
using Xunit;
11+
12+
public class LibreTranslateTranslatorTests
13+
{
14+
[Fact]
15+
public void LibreTranslateParsesResponseCorrectly()
16+
{
17+
const string input = "{\"translatedText\":\"¡Hola!\"}";
18+
19+
var result = Translators.LibreTranslateTranslator.ParseResponse(input).First();
20+
21+
Assert.Equal("¡Hola!", result);
22+
}
23+
24+
[Fact]
25+
public void LibreTranslateParsesResponseWithDetectedLanguageCorrectly()
26+
{
27+
const string input = "{\"detectedLanguage\":{\"confidence\":90.0,\"language\":\"fr\"},\"translatedText\":\"Hello!\"}";
28+
29+
var result = Translators.LibreTranslateTranslator.ParseResponse(input).First();
30+
31+
Assert.Equal("Hello!", result);
32+
}
33+
34+
[Fact]
35+
public void LibreTranslateParsesEmptyResponseCorrectly()
36+
{
37+
const string input = "{}";
38+
39+
var result = Translators.LibreTranslateTranslator.ParseResponse(input);
40+
41+
Assert.Null(result);
42+
}
43+
44+
[Fact]
45+
public void LibreTranslateParsesNullTranslatedTextCorrectly()
46+
{
47+
const string input = "{\"translatedText\":null}";
48+
49+
var result = Translators.LibreTranslateTranslator.ParseResponse(input);
50+
51+
Assert.Null(result);
52+
}
53+
54+
[Fact]
55+
public void LibreTranslateParsesResponseWithAlternativesCorrectly()
56+
{
57+
const string input = "{\"translatedText\":\"Hi\",\"alternatives\":[\"Hello\",\"Hey\"]}";
58+
59+
var result = Translators.LibreTranslateTranslator.ParseResponse(input);
60+
61+
Assert.Equal(["Hi", "Hello", "Hey"], result);
62+
}
63+
64+
[Fact]
65+
public void LibreTranslateThrowsOnBadJson()
66+
{
67+
const string input = """
68+
{"translatedText":
69+
""";
70+
71+
Assert.ThrowsAny<Exception>(() => Translators.LibreTranslateTranslator.ParseResponse(input));
72+
}
73+
74+
#region Integration Tests (require local LibreTranslate instance on localhost:5000)
75+
76+
private const string LocalLibreUrl = "http://localhost:5000";
77+
78+
private static async Task<bool> IsLibreTranslateAvailableAsync()
79+
{
80+
try
81+
{
82+
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
83+
var response = await httpClient.GetAsync($"{LocalLibreUrl}/languages");
84+
return response.IsSuccessStatusCode;
85+
}
86+
catch
87+
{
88+
return false;
89+
}
90+
}
91+
92+
[Fact]
93+
public async Task TranslateEnglishToGerman_ReturnsTranslatedText()
94+
{
95+
if (!await IsLibreTranslateAvailableAsync())
96+
{
97+
// Skip test if LibreTranslate is not running locally
98+
return;
99+
}
100+
101+
var result = await Translators.LibreTranslateTranslator.TranslateAsync(
102+
$"{LocalLibreUrl}/translate",
103+
apiKey: null,
104+
text: "Good morning",
105+
sourceLanguage: CultureInfo.GetCultureInfo("en"),
106+
targetLanguage: CultureInfo.GetCultureInfo("de"),
107+
alternatives: null,
108+
cancellationToken: CancellationToken.None);
109+
110+
Assert.NotNull(result);
111+
Assert.NotEmpty(result);
112+
Assert.NotEmpty(result[0]);
113+
114+
// "Good morning" in German is typically "Guten Morgen"
115+
Assert.Contains("Guten Morgen", result[0], StringComparison.OrdinalIgnoreCase);
116+
}
117+
118+
[Fact]
119+
public async Task TranslateWithCancellation_ThrowsOperationCanceledException()
120+
{
121+
if (!await IsLibreTranslateAvailableAsync())
122+
{
123+
// Skip test if LibreTranslate is not running locally
124+
return;
125+
}
126+
127+
using var cts = new CancellationTokenSource();
128+
cts.Cancel();
129+
130+
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
131+
await Translators.LibreTranslateTranslator.TranslateAsync(
132+
LocalLibreUrl,
133+
apiKey: null,
134+
text: "Hello",
135+
sourceLanguage: CultureInfo.GetCultureInfo("en"),
136+
targetLanguage: CultureInfo.GetCultureInfo("es"),
137+
alternatives: null,
138+
cancellationToken: cts.Token));
139+
}
140+
141+
#endregion
142+
}

0 commit comments

Comments
 (0)