Erweiterung meines Blogs mit Übersetzungen durch Amazon Nova

2025-07-15
This post cover image
#aws
#cloud
#genai
#serverless

Diese Datei wurde automatisch von KI übersetzt, es können Fehler auftreten

Lesen Sie diesen Beitrag auf Englisch? Oder vielleicht auf Spanisch? Oder warum nicht auf Italienisch?

In meinem vorherigen Beitrag über die Automatisierung von Aufgaben für meinen Blog habe ich automatisches Korrekturlesen mit Amazon Nova hinzugefügt. Obwohl das Schreiben auf Englisch mir ermöglicht, mit einer großen technischen Zielgruppe in Verbindung zu treten, gibt es eine noch größere globale Community, die von meinen Inhalten profitieren könnte, wenn sie in ihrer Muttersprache verfügbar wären.

Ich habe meinen Blog bereits mit Sprachgenerierung mit Amazon Polly erweitert. Das habe ich getan, damit Menschen, die Schwierigkeiten beim Lesen haben, wie zum Beispiel Menschen mit Legasthenie, von meinen Inhalten lernen können, ohne Schwierigkeiten beim Lesen zu haben, sondern sie stattdessen anzuhören.

Dieses Mal breche ich Sprachbarrieren und mache meine technischen Inhalte für Entwickler und Cloud-Enthusiasten auf der ganzen Welt durch automatische Übersetzung mit Amazon Nova zugänglich.

Vision

Die technische Community ist unglaublich vielfältig, und obwohl Englisch in der Tech-Welt meist verwendet wird, ist bei weitem nicht jeder Muttersprachler. Ich bin es nicht, und bei weitem nicht jeder fühlt sich zu 100 % wohl dabei, Inhalte auf Englisch zu konsumieren. Sie ziehen es vor, sie in ihrer Muttersprache zu lesen. Komplexe technische Konzepte können schon schwierig genug sein, ohne die zusätzliche Hürde des Sprachverständnisses.

In meinem Beitrag Serverlose Statistiklösung mit Lambda@Edge erkläre ich, wie ich ein serverloses Analysesystem für meinen Blog aufgebaut habe, mit dem ich sehen konnte, woher meine Leser kommen und vieles mehr.

Wenn ich mir meine Analysen ansehe, kann ich viele Leser aus Deutschland, Österreich, Frankreich, Italien, Spanien (und anderen spanischsprachigen Ländern) sowie Brasilien und Portugal sehen. Deshalb wollte ich eine Lösung schaffen, bei der die Leute wählen können, ob sie auf Englisch oder in einer anderen Sprache lesen möchten.

Übersichtsstatistiken der Länder

Für diese erste Implementierung eines Übersetzungsdienstes habe ich mich dafür entschieden, fünf Sprachen zu unterstützen, die einen großen Teil meiner Leser repräsentieren: Deutsch, Spanisch, Französisch, Italienisch und Portugiesisch. In einer zweiten Phase werde ich möglicherweise zusätzliche Sprachen wie Japanisch hinzufügen.

Wichtig war, dass die Lösung:

Technische Genauigkeit beibehalten: Technische Begriffe und AWS-Dienstnamen müssen korrekt übersetzt werden.
Markdown-Struktur beibehalten: Alle Formatierungen, Codeblöcke, Links und Bilder müssen intakt bleiben.
Meinen Schreibstil beibehalten: Die Übersetzung sollte natürlich klingen und gleichzeitig die technische Tiefe bewahren.
Effizient skalieren: Unterstützung mehrerer Sprachen ohne Vervielfachung meiner Arbeitsbelastung.
Nahtlos integrieren: Passt in meine bestehende serverlose und ereignisgesteuerte Pipeline.

Amazon Nova für Übersetzungen

Nachdem ich Amazon Nova Pro erfolgreich für das Korrekturlesen verwendet habe, war ich mir sicher, dass es auch meine Übersetzungsaufgaben bewältigen kann. Die multimodalen Fähigkeiten von Nova Pro und das Verständnis des Kontexts machen es für die Übersetzung technischer Inhalte gut geeignet. Mehr über Nova, verschiedene Modelle und BedRock erfahren Sie in meinem Beitrag automatisches Korrekturlesen mit Amazon Nova.

Lösungsarchitektur

Basierend auf meiner bestehenden ereignisgesteuerten Architektur habe ich die Pipeline um die Übersetzung erweitert. Als ich die Pipeline erstellte, baute ich sie auf eine sehr modulare Weise auf, sodass sie leicht erweitert werden konnte. Dieser Ansatz zahlt sich jetzt wirklich aus, da ich Erweiterungen nahtlos und mit sehr wenig Aufwand in den gleichen Ablauf einfügen kann.

Die Übersetzungen laufen parallel zur Generierung von Sprache und Quiz, falls ein Pull-Request ausgeführt wird. Ich habe mich jedoch entschieden, sie vom Standard-Saga-Flow zu trennen, den ich für Sprache und Quiz verwende. Der Grund dafür ist, dass ich die Übersetzungen nicht nur bei Pull-Requests ausführen, sondern auch Übersetzungen älterer Beiträge aufrufen möchte.

Übersetzungs-Pipeline-Architektur

Im Falle des Pull-Request-Kontexts beginnt der Ablauf nach Abschluss des Korrekturlesens durch ein Ereignis von GitHub Actions. Ich kann den Ablauf aber auch aufrufen und starten, indem ich ein Ereignis mit einem Branch-Namen und dem zu übersetzenden Blog an den Informationsdienst sende, der dann den gesamten Ablauf startet. Eine Ergänzung in diesem Fall ist, dass ich in einem letzten Schritt auch einen neuen Pull-Request mit den Übersetzungen für den alten Beitrag öffne.

Die Übersetzungen werden in sprachspezifischen Verzeichnissen (z. B. /de/, /es/, /fr/) gespeichert, was mir ermöglicht, alles während des Build-Prozesses zu organisieren. Ich verwende 11ty, um statisches HTML aus meinen in Markdown geschriebenen Beiträgen zu generieren.

Technischer Tiefgang

Die Übersetzung wird als eigener Dienst implementiert und basiert auf Step Functions als Hauptdienst und Orchestrator. Die Übersetzung beginnt entweder damit, dass ich einen neuen Branch erstelle und den Ablauf für einen älteren Beitrag starte, oder sie wird automatisch gestartet, wenn ein neuer Pull-Request für einen neuen Beitrag geöffnet wird. So entsteht ein Übersetzungsablauf.

Übersetzungs-Pipeline-Architektur

Übersetzungs-Step-Function

Der Kern des Übersetzungsdienstes ist diese Step Function, die den gesamten Ablauf orchestriert. Wenn Sie mir schon länger folgen, wissen Sie, dass ich normalerweise versuche, Step Functions SDK-Integrationen so weit wie möglich zu verwenden, aber für den Übersetzungsablauf muss ich mich stark auf meinen zweitliebsten Dienst, Lambda, verlassen.

Übersicht über die Übersetzungs-Step-Functions

Der Workflow besteht aus mehreren Schlüsselzuständen:

  1. CollectInfo: Normalisiert Eingabedaten und bereitet sie für Übersetzungen vor, liest Sprachen aus dem Ereignis oder verwendet Standardwerte.
  2. CheckoutMarkdownFile: Lädt die Quell-Markdown-Datei von GitHub herunter.
  3. Iterate Languages: Führt Übersetzungen für die Zielsprachen durch, begrenzt auf 1 parallele Übersetzung im Moment.
  4. CreateCommit: Committet alle übersetzten Dateien zurück in das Repository, öffnet bei Bedarf einen neuen Pull-Request.

Bei der Definition der Step Function verwende ich die Unterstützung für die JSONata-Abfragesprache, die zusammen mit Variablen eine der besten Ergänzungen zu Step Functions ist. Der CollectInfo-Zustand speichert Informationen in Variablen, die für alle zukünftigen Zustände verfügbar sind. Bei der Iteration über Sprachen verwende ich einen Map-Zustand mit einer auf 1 begrenzten Grenze.

Comment: Übersetzen von Blog-Inhalten mit Bedrock.
QueryLanguage: JSONata
StartAt: CollectInfo
States:
  CollectInfo:
    Type: Pass
    Assign:
      Languages: "{% $exists($states.input.detail.Data.Translate.Languages) ? $states.input.detail.Data.Translate.Languages : ['de','es','fr','it','pt'] %}"
      GitInfo:
        CommitSha: "{% $exists($states.input.detail.Data.GitInfo.CommitSha) ? $states.input.detail.Data.GitInfo.CommitSha : $states.input.detail.Data.PullRequestInfo.PullRequestCommitSha %}"
        Branch: "{% $exists($states.input.detail.Data.GitInfo.Branch) ? $states.input.detail.Data.GitInfo.Branch : $states.input.detail.Data.PullRequestInfo.PullRequestBranch %}"
      MarkdownInfo:
        FilePath: "{% $states.input.detail.Data.MarkdownFile.path %}"
        FileSlug: "{% $states.input.detail.Data.MarkdownFile.fileSlug %}"
    Next: CheckoutMarkdownFile
  CheckoutMarkdownFile:
    Type: Task
    Resource: arn:aws:states:::lambda:invoke
    Arguments:
      FunctionName: ${CheckoutMarkdownFileFunctionArn}
      Payload:
        GitInfo: "{% $GitInfo %}"
        MarkdownInfo: "{% $MarkdownInfo %}"
    Next: Iterate Languages
  Iterate Languages:
    Type: Map
    ItemProcessor:
      ProcessorConfig:
        Mode: INLINE
      StartAt: TranslateMarkdownFile
      States:
        TranslateMarkdownFile:
          Type: Task
          Resource: arn:aws:states:::lambda:invoke
          Arguments:
            FunctionName: ${TranslateBedrockFunctionArn}
            Payload:
              Key: "{%$MarkdownInfo.FileSlug & '/' & $MarkdownInfo.FilePath %}"
              S3Bucket: ${ETLBucket}
              Language: "{% $states.input %}"
          End: true
    Next: CreateCommit
    MaxConcurrency: 1
    Items: "{% $Languages %}"
  CreateCommit:
    Type: Task
    Resource: arn:aws:states:::lambda:invoke
    Arguments:
      FunctionName: ${CreateCommitFunctionArn}
      Payload:
        GitInfo: "{% $GitInfo %}"
        MarkdownInfo: "{% $MarkdownInfo %}"
    End: true

Lambda-Funktionen

Bei den Lambda-Funktionen gibt es drei Hauptfunktionen in der Lösung. Die Funktion, die Octokit zum Auschecken des Codes verwendet, und die Funktion, die den neuen Commit erstellt. Beide wurden in früheren Beiträgen zu diesem Thema erklärt und werden dieses Mal ausgelassen.

Übersetzungsfunktion

Dies ist das Herzstück der gesamten Lösung und wird mit Bedrock interagieren, um den Beitrag zu übersetzen. Der übersetzte Beitrag wird in einem sprachspezifischen Verzeichnis gespeichert.

import json
import os
import boto3
from botocore.exceptions import ClientError

def handler(event, context):
    try:
        bucket = event.get("S3Bucket")
        key = event.get("Key")
        lang = event.get("Language", "error")

        if lang not in ["de", "es", "fr", "it", "pt"]:
            raise ValueError(
                f"Nicht unterstützte Sprache '{lang}'. Unterstützte Sprachen sind: de, es, fr, it, pt."
            )

        s3_client = boto3.client("s3")
        bedrock_client = boto3.client("bedrock-runtime", region_name="eu-west-1")

        response = s3_client.get_object(Bucket=bucket, Key=key)
        file_content = response["Body"].read().decode("utf-8")

        prompt = f"""Sie sind ein professioneller technischer Redakteur und Übersetzer, Experte für AWS und Cloud Computing. Bitte übersetzen Sie sorgfältig den folgenden Markdown-Blogbeitrag in {lang}. 
        Anweisungen:
        - Behalten Sie die ursprüngliche Markdown-Formatierung bei
        - Behalten Sie den Ton und Stil bei
        - Ändern Sie nicht die Bedeutung oder Struktur des Inhalts
        - Geben Sie nur den übersetzten Markdown-Text ohne zusätzliche Kommentare zurück
        - Übersetzen Sie keine Codeblöcke oder Inline-Code
        - Achten Sie auf korrekte Grammatik, Rechtschreibung und Zeichensetzung
        - Wenn der Inhalt bereits in {lang} ist, ignorieren Sie ihn und geben Sie den Inhalt wie er ist zurück
        - Umgeben Sie die übersetzte Version nicht mit ```markdown

        Hier ist der zu übersetzende Markdown-Inhalt:

        {file_content}"""

        # Bereiten Sie die Anfrage für das Nova Pro-Modell vor
        request_body = {
            "messages": [{"role": "user", "content": [{"text": prompt}]}],
            "inferenceConfig": {
                "temperature": 0.1,  # Niedrige Temperatur für konsistente Übersetzungen
                "topP": 0.9,
                "maxTokens": 10240,  # Anpassung basierend auf der erwarteten Ausgabelänge
            },
        }

        bedrock_response = bedrock_client.invoke_model(
            modelId="eu.amazon.nova-pro-v1:0",
            body=json.dumps(request_body),
            contentType="application/json",
        )

        response_body = json.loads(bedrock_response["body"].read())
        translated_content = response_body["output"]["message"]["content"][0]["text"]
        
        new_key = f"{lang}/{key}"

        s3_client.put_object(
            Bucket=bucket,
            Key=new_key,
            Body=translated_content.encode("utf-8"),
            ContentType="text/markdown",
        )

        return {
            "statusCode": 200,
            "body": "Übersetzung abgeschlossen",
        }

    except Exception as e:
        print(f"Unerwarteter Fehler: {str(e)}")
        return {
            "statusCode": 500,
            "body": json.dumps({"error": "Interner Serverfehler", "message": str(e)}),
        }

Übersetzungsqualität und Prompt-Engineering

Während der Implementierung der Korrekturleselösung habe ich gelernt, wie wichtig der Teil des Prompt-Engineerings für das Endergebnis ist. Die KI tut immer noch nur das, was Sie ihr sagen, und wenn Ihr Prompt nicht spezifisch genug ist, kann die Qualität des Ergebnisses variieren. Unter Verwendung der Erkenntnisse aus dem Korrekturlesen enthielt mein Prompt einige spezifische Anforderungen.

Am Ende lieferte mir der folgende Prompt einige großartige Ergebnisse:

Sie sind ein professioneller technischer Redakteur und Übersetzer, Experte für AWS und Cloud Computing. 
Bitte übersetzen Sie sorgfältig den folgenden Markdown-Blogbeitrag in {lang}. 
Anweisungen:
- Behalten Sie die ursprüngliche Markdown-Formatierung bei
- Behalten Sie den Ton und Stil bei
- Ändern Sie nicht die Bedeutung oder Struktur des Inhalts
- Geben Sie nur den übersetzten Markdown-Text ohne zusätzliche Kommentare zurück
- Übersetzen Sie keine Codeblöcke oder Inline-Code
- Achten Sie auf korrekte Grammatik, Rechtschreibung und Zeichensetzung
- Wenn der Inhalt bereits in {lang} ist, ignorieren Sie ihn und geben Sie den Inhalt wie er ist zurück
- Umgeben Sie die übersetzte Version nicht mit ```markdown

Hier ist der zu übersetzende Markdown-Inhalt:

11ty-Änderungen

Ich verwende 11ty als statischen HTML-Generator, alle Blogs werden in Markdown geschrieben und dann in HTML konvertiert. Um verschiedene Sprachen unterstützen zu können, musste ich einige Änderungen an meinem 11ty-Build und -Layout vornehmen. Zunächst habe ich dem Front Matter ein neues Feld hinzugefügt, das angibt, in welcher Sprache der Beitrag verfasst ist. Dann musste ich ändern, wie meine Post-Collection Blog-Beiträge abruft.

Ich habe einen Filter hinzugefügt, damit ich nur die Beiträge auf Englisch abruf, damit ich nicht alle Beiträge in der Sammlung erhalte.

export default (coll) => {
  const posts = coll
    .getFilteredByGlob("src/posts/*.md")
    .filter((p) => (p.data.lang || "en") === "en");

  return posts.reverse();
};

Als Nächstes musste ich alle Übersetzungen für einen Beitrag lokalisieren. Ich habe den Datei-Slug als Schlüssel verwendet, um diese Sammlung zu erstellen.

config.addCollection("postsBySlug", (collection) => {
  const posts = collection.getFilteredByGlob("src/posts/**/*.md");
  const postsBySlug = {};

  posts.forEach((post) => {
    const pathParts = post.inputPath.split("/");
    const filename = pathParts[pathParts.length - 1]; 
    const baseSlug = filename.replace(".md", "");

    if (!postsBySlug[baseSlug]) {
      postsBySlug[baseSlug] = [];
    }
    postsBySlug[baseSlug].push(post);
  });

  return postsBySlug;
});

Damit konnte ich einen Sprachindikator erstellen und einschließen, der der Beitrags-Grid Flaggen hinzufügt, die auf Übersetzungen hinweisen. Jede Flagge ist anklickbar und führt Sie direkt zum übersetzten Beitrag.

{% set item = post or talk %}
{% set pathParts = item.inputPath.split('/') %}
{% set filename = pathParts[pathParts.length - 1] %}
{% set baseSlug = filename.replace('.md', '') %}
{% set currentLang = item.data.lang or 'en' %}

{# Sprachindikator nur für Beiträge anzeigen (nicht für Talks oder Links) #}
{% if item.inputPath.includes('/posts/') %}
{% set availablePosts = collections.postsBySlug[baseSlug] %}
{% if availablePosts.length > 1 %}
<div class="mx-auto flex items-center flex-wrap">
    <div class="justify-start items-center flex-grow w-full md:w-auto md:flex">
        <p class="text-gray-700 mb-1 font-medium" title="Verfügbare Sprachen">
            <i class="fas fa-globe text-teal-600 fill-current"></i> 
            {% for translatedPost in availablePosts %}
                {% set postLang = translatedPost.data.lang or 'en' %}
                <a href="{{ translatedPost.url }}" class="text-sm ml-1 hover:scale-110 transition-transform duration-200 inline-block" title="{{ postLang | getLanguageName }}">{{ postLang | getLanguageFlag }}</a>
            {% endfor %}
        </p>
    </div>
</div>
{% endif %}
{% endif %}

Das Ergebnis:

Sprachanzeige in der Beitrags-Grid

Schließlich habe ich dem eigentlichen Beitrags-Layout ein Dropdown-Menü zusammen mit einem Haftungsausschluss hinzugefügt, dass der Beitrag von einer KI übersetzt wurde.

Sprach-Dropdown-Layout im Beitrag

Mit diesen Änderungen können Sie auswählen, in welcher Sprache Sie jeden Beitrag lesen möchten.

Fazit

Die automatisierte Übersetzung zu meinem Blog hinzuzufügen, fühlte sich wie der nächste Schritt an, um meine Inhalte für alle zugänglich zu machen. Durch die Nutzung von Amazon Nova Pro und meiner bestehenden serverlosen Architektur habe ich eine skalierbare Übersetzungslösung geschaffen, ohne die betriebliche Komplexität zu erhöhen. Ob Sie nun einen technischen Blog, eine Dokumentation oder eine beliebige Content-Plattform erstellen, automatisierte Übersetzungen könnten Ihre Reichweite erweitern.

Warum habe ich nicht die gesamte Website übersetzt? Das war eine Route, die ich zuerst erkundet habe, aber ich hatte das Gefühl, dass der Hauptvorteil darin bestehen würde, die Beiträge übersetzen zu lassen. Es gibt mir auch die Freiheit zu entscheiden, ob ich alle älteren Beiträge übersetzen möchte oder nicht, und ich kann Übersetzungen nachträglich hinzufügen. Eines Tages werde ich vielleicht alles übersetzen, aber im Moment lag der Fokus auf den einzelnen Beiträgen.

Abschließende Worte

Vergessen Sie nicht, mir auf LinkedIn zu folgen und für weitere Inhalte lesen Sie den Rest meiner Blogs.

Wie Werner sagt! Jetzt bauen Sie los!

Dieser Beitrag wurde ursprünglich auf Englisch geschrieben und automatisch in mehrere Sprachen übersetzt, indem das hier beschriebene System verwendet wurde. Der Übersetzungsprozess dauerte weniger als 5 Minuten und erforderte keinen manuellen Eingriff.