To be completely honest: I do not really like automatic translations. Especially if you are not born in an english-speaking country, translations have been a real pain from the beginning. MSDN was one of those contenders that did really, really bad translations, years before AI was something to talk about.
Now with AI the translations get better, but there are still bad ones and too many contents get translated. YouTube is nearly not usable anymore from germany if you are interested in original content.
What way to go?
Having said that I still think a translation can be something really useful. First of all it has to be good. And second requirement is that users have a choice.
So I decided to add translations to my blogpost. I stopped blogging in two languages in 2019, so all german entries have been very old. Time to check if there are good solutions out there.
Because I translated a book I already had some comparisons. At least from german to english currently my favorite tool was Claude.io. I did not really like the quality of DeepL and ChatGPT had good results but was very slow. In addition: Claude really seems to like MarkDown. Which is helpful in this case.
This time I wanted to use the API to automatically translate my Grav-Blogitems. Bad news is: You need a paid account for that. Good news is: It does not really matter how many money you use to fill your wallet and it is quite cheap.
My 50KB of data made up about 20 Cents. Of course your milage may vary. This is not sponsored, BTW.
Preparations
After creating an account, you can use the NuGet-Package Anthrophic.SDK. You simply create a new Client with your secret and can start with some messages. If you ever worked with OpenAi-API or Llama creating the message is nearly the same:
var client = new AnthropicClient(new APIAuthentication(_apiSecret));
var messages = new List<Message>()
{
new Message(RoleType.User, "Bitte übersetze das folgende Markdown nach Deutsch.");
),
new Message(RoleType.User, markdown)
};
In case you are curious the message simply tells to translate a markdown into german
Then you add the parameters. Temperature is - as known for AI-APIS how "creative" the AI is allowed to get. MaxTokens
is a required value and can't be too high:
var pars = new MessageParameters()
{
Messages = messages,
MaxTokens = 1024,
Model = AnthropicModels.Claude35Sonnet,
Stream = false,
Temperature = 1m,
};
Getting the response now is easy:
var response = await client.Messages.GetClaudeMessageAsync(pars);
What I don't like: If anything is wrong with your account you do not really receive a useful error code but simply null
I had this problem before putting some money into the account.
The response is now in response.Message
. The result is great, but long texts just stop after a while. Response.StopReason
tells us why:
max_tokens
The obvious solution would be to increase the allowed tokens but as I said MaxTokens has a limited range. So you have to catch that error and go into a loop. Fun thing is: Claude "forgets" what he said before. So you have to tell him and ask to continue afterwards:
while (response.StopReason == "max_tokens")
{
pars.Messages.Add(new Message(RoleType.Assistant, response.Message));
pars.Messages.Add(new Message(RoleType.User, "bitte fahre fort"));
response = await client.Messages.GetClaudeMessageAsync(pars);
}
That`s it. Not really that complicated after all.
AI doing AI things
While the result is quite good I had a few issues. Being a coder it is a bit of a learning curve that errors cannot be fixed through coding but only by messages. The first was that the translation is quite formal. An additional message changed that:
pars.Messages.Add(new Message(RoleType.User, "Enthält der Text eine persönliche Anrede, verwende 'Du' und nicht 'Sie'. ");
(I will not be able to translate this command. It is a really german thing :) )
Another issue is that Claude likes to explain things. So we have to tell him to stop doing that:
pars.Messages.Add(new Message(RoleType.User, "Antworte ausschließlich mit dem Dokument, ergänze keine Erklärungen. ");
(Which translates into 'Only reply with the document, do not explain anything')
Surprisingly I had to add no additional message to not translate code. If you translate apt-get install
into german apt-get installiere
for example that would not work. I saw this happen on other sites but Claude did not need any additional hints.
On the other hand. Claude sometimes decided that my code wasn't interesting enough:
[Rest des Dokuments enthält die gleichen Code-Blöcke und technischen Anweisungen wie im Original, nur der erklärende Text wurde ins Deutsche übersetzt]
Which roughly translates into "the rest of the document contains the same code as the original. Did not translate that". Did not expect that, but that was fixable as well:
pars.Messages.Add(new Message(RoleType.User, "Fasse keine Inhalte zusammen, sondern gebe alles aus. Ziel soll sein dass man das deutschsprachige Dokument genauso alleine versteht wie das englischsprachige "):
which roughly translates into "Do not summarize contents. The goal should be that the german document can stand on its own"
Now the result is exactly as needed. For completionists sake here is the complete code:
private async Task<string> TranslateSingleFile(string source)
{
var translated=string.Empty;
var markdown = File.ReadAllText(source);
var client = new AnthropicClient(new APIAuthentication(_apiSecret));
var messages = new List<Message>()
{
new Message(RoleType.User, "Bitte übersetze das folgende Markdown nach Deutsch."
+" Antworte ausschließlich mit dem Dokument, ergänze keine Erklärungen. "
+ "Enthält der Text eine persönliche Anrede, verwende 'Du' und nicht 'Sie'. "
+"Fasse keine Inhalte zusammen, sondern gebe alles aus. Ziel soll sein dass man das deutschsprachige Dokument genauso alleine versteht wie das englischsprachige "
),
new Message(RoleType.User, markdown)
};
var pars = new MessageParameters()
{
Messages = messages,
MaxTokens = 1024,
Model = AnthropicModels.Claude35Sonnet,
Stream = false,
Temperature = 1m,
};
var response = await client.Messages.GetClaudeMessageAsync(pars);
if (response?.Message == null) throw new Exception("No valid response from Claude");
translated += response.Message.ToString();
while (response.StopReason == "max_tokens")
{
pars.Messages.Add(new Message(RoleType.Assistant, response.Message));
pars.Messages.Add(new Message(RoleType.User, "bitte fahre fort"));
response = await client.Messages.GetClaudeMessageAsync(pars);
translated += response.Message.ToString();
}
if (response.StopReason != "end_turn") throw new Exception($"Translation failed. Stop reason:{response.StopReason}");
return translated;
}