Ampliando Mi Blog con Traducciones de Amazon Nova

Questo file è stato tradotto automaticamente dall'IA, potrebbero verificarsi errori
¿Estás leyendo esta publicación en inglés? ¿O tal vez en español? ¿O por qué no en italiano?
En mi publicación anterior sobre la automatización de tareas para mi blog, agregué la corrección ortográfica automatizada utilizando Amazon Nova. Aunque escribir en inglés me permite conectarme con una gran audiencia técnica, hay una comunidad global aún más grande que podría beneficiarse de mi contenido si estuviera disponible en sus idiomas nativos.
Anteriormente extendí mi blog con generación de voz utilizando Amazon Polly. Hice eso para que las personas que tienen dificultades para leer, como aquellas con dislexia, pudieran aprender de mi contenido sin tener dificultades para leerlo, y en su lugar escucharlo.
Esta vez, estoy rompiendo las barreras del idioma y haciendo que mi contenido técnico sea accesible para desarrolladores y entusiastas de la nube en todo el mundo a través de la traducción automatizada utilizando Amazon Nova.
Visión
La comunidad técnica es increíblemente diversa, y aunque el inglés se usa principalmente en el mundo tecnológico, no todos son hablantes nativos de inglés. Yo no lo soy, y no todos se sienten 100% cómodos consumiendo contenido en inglés. Prefieren leerlo en su idioma nativo. Los conceptos técnicos complejos pueden ser lo suficientemente desafiantes sin la barrera adicional de la comprensión del idioma.
En mi publicación Solución de estadísticas sin servidor con Lambda@Edge, explico cómo construí un sistema de análisis sin servidor para mi blog, donde podía ver de dónde son mis lectores y más.
Al mirar mis estadísticas, puedo ver muchos lectores de Alemania, Austria, Francia, Italia, España (y otros países de habla hispana), y Brasil y Portugal. Por lo tanto, quería crear una solución donde las personas pudieran elegir si les gustaría leer en inglés o en un idioma diferente.
Para esta primera implementación de un servicio de traducción, elegí admitir cinco idiomas que representan una gran parte de mis lectores: alemán, español, francés, italiano y portugués. Podría agregar idiomas adicionales como el japonés en una segunda fase.
Lo que era importante es que la solución:
Mantuviera la precisión técnica: Los términos técnicos y los nombres de los servicios de AWS deben traducirse correctamente.
Preservara la estructura markdown: Todo el formato, bloques de código, enlaces e imágenes deben permanecer intactos.
Mantuviera mi estilo de escritura: La traducción debería sentirse natural mientras mantiene la profundidad técnica.
Escalara eficientemente: Admitir múltiples idiomas sin multiplicar mi carga de trabajo.
Se integrara sin problemas: Encajar en mi canal existente sin servidor e impulsado por eventos.
Amazon Nova para Traducción
Habiendo utilizado con éxito Amazon Nova Pro para la corrección ortográfica, estaba seguro de que también podría manejar mis tareas de traducción. Las capacidades multimodales de Nova Pro y su comprensión del contexto lo hacen muy adecuado para traducir contenido técnico. Puedes leer más sobre Nova, diferentes modelos y BedRock en mi publicación corrección ortográfica automatizada utilizando Amazon Nova.
Arquitectura de la Solución
Basándome en mi arquitectura impulsada por eventos existente, extendí el canal para incluir la traducción. Cuando creé el canal, lo construí de una manera muy modular, por lo que podría extenderse fácilmente. Este enfoque realmente está dando sus frutos ahora, ya que puedo conectar extensiones sin problemas en el mismo flujo con muy poco esfuerzo.
Las traducciones se ejecutan en paralelo con la generación de voz y cuestionario en caso de ejecutar una solicitud de extracción. Sin embargo, decidí separarlo del flujo de saga estándar que uso para la voz y el cuestionario. La razón de esto es que me gustaría poder ejecutar las traducciones no solo en solicitudes de extracción, sino también invocar traducciones de publicaciones más antiguas.
Entonces, en el caso del contexto de la solicitud de extracción, el flujo comienza cuando se completa la corrección ortográfica, por un evento de GitHub Actions. Pero también puedo invocar e iniciar el flujo enviando un evento con un nombre de rama y el blog para traducir, al servicio de información que luego inicia todo el flujo. Una adición en ese caso es que en un último paso, también abriré una nueva solicitud de extracción con las traducciones para la publicación antigua.
Las traducciones se almacenan en directorios específicos del idioma (por ejemplo, /de/
, /es/
, /fr/
), lo que me permite organizar todo durante el proceso de compilación. Uso 11ty para generar HTML estático a partir de mis publicaciones escritas en markdown.
Profundización Técnica
La traducción se implementa como su propio servicio y se basa en Step Functions como el servicio principal y orquestador. La traducción comienza ya sea cuando creo una nueva rama y inicio el flujo para una publicación más antigua, o se inicia automáticamente cuando se abre una nueva solicitud de extracción, para una nueva publicación. Creando un flujo de traducción como este.
Función de Paso de Traducción
El núcleo del servicio de traducción es esta Función de Paso, que orquesta todo el flujo. Si me han seguido por un tiempo, saben que normalmente trato de usar las integraciones del SDK de Step Functions tanto como sea posible, pero para el flujo de traducción, necesito confiar mucho en mi segundo servicio favorito, Lambda.
El flujo de trabajo consta de varios estados clave:
- CollectInfo: Normaliza los datos de entrada y se prepara para las traducciones, lee los idiomas del evento o usa el valor predeterminado.
- CheckoutMarkdownFile: Descarga el archivo markdown de origen de GitHub.
- Iterate Languages: Ejecuta traducciones para los idiomas de destino, limitado a 1 traducción paralela en este momento.
- CreateCommit: Confirma todos los archivos traducidos de vuelta al repositorio, abre una nueva solicitud de extracción si es necesario.
Al mirar la definición de la Función de Paso, estoy usando el soporte para el lenguaje de consulta JSONata, que es una de las mejores adiciones a Step Functions junto con las variables. El estado CollectInfo
almacenará información en variables que estarán disponibles para todos los estados futuros. Iterando sobre los idiomas, estoy usando un estado Map
con un límite establecido en 1.
Comment: Traducir el contenido de la publicación utilizando 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
Funciones Lambda
Al mirar las funciones Lambda, hay tres funciones principales en juego en la solución. La función que usará Octokit para revisar el código, y la función que creará el nuevo commit. Ambas han sido explicadas en publicaciones anteriores sobre este tema y se omiten esta vez.
Función de Traducción
Este es el corazón y el alma de toda la solución e interactuará con Bedrock para traducir la publicación. La publicación traducida se almacenará en un directorio específico del idioma.
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"Idioma no compatible '{lang}'. Idiomas compatibles son: 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"""Eres un editor técnico profesional y traductor, experto en AWS y computación en la nube. Por favor, traduce cuidadosamente la siguiente publicación de blog en markdown a {lang}.
Instrucciones:
- Mantén el formato markdown original
- Mantén el tono y el estilo consistentes
- No cambies el significado o la estructura del contenido
- Devuelve solo el texto markdown traducido sin ningún comentario adicional
- No traduzcas bloques de código o código en línea
- Asegúrate de una gramática, ortografía y puntuación adecuadas
- Si el contenido ya está en {lang}, simplemente ignóralo y devuelve el contenido tal como está
- No rodees la versión traducida con ```markdown
Aquí está el contenido markdown para traducir:
{file_content}"""
# Prepara la solicitud para el modelo Nova Pro
request_body = {
"messages": [{"role": "user", "content": [{"text": prompt}]}],
"inferenceConfig": {
"temperature": 0.1, # Temperatura baja para traducciones consistentes
"topP": 0.9,
"maxTokens": 10240, # Ajusta según la longitud de salida esperada
},
}
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": "Traducción realizada",
}
except Exception as e:
print(f"Error inesperado: {str(e)}")
return {
"statusCode": 500,
"body": json.dumps({"error": "Error interno del servidor", "message": str(e)}),
}
Calidad de la Traducción e Ingeniería de Prompts
Durante la implementación de la solución de corrección ortográfica, aprendí lo importante que era la parte de ingeniería de prompts para el resultado final. La IA aún hace solo lo que le dices que haga, y si tu prompt no es lo suficientemente específico, la calidad del resultado puede variar. Usando los aprendizajes de la corrección ortográfica, mi prompt incluyó algunos requisitos específicos.
Al final, el siguiente prompt me dio algunos excelentes resultados:
Eres un editor técnico profesional y traductor, experto en AWS y computación en la nube.
Por favor, traduce cuidadosamente la siguiente publicación de blog en markdown a {lang}.
Instrucciones:
- Mantén el formato markdown original
- Mantén el tono y el estilo consistentes
- No cambies el significado o la estructura del contenido
- Devuelve solo el texto markdown traducido sin ningún comentario adicional
- No traduzcas bloques de código o código en línea
- Asegúrate de una gramática, ortografía y puntuación adecuadas
- Si el contenido ya está en {lang}, simplemente ignóralo y devuelve el contenido tal como está
- No rodees la versión traducida con ```markdown
Aquí está el contenido markdown para traducir:
Cambios en 11ty
Estoy usando 11ty como el generador de HTML estático, todos los blogs están escritos en markdown y luego convertidos a HTML. Para poder admitir diferentes idiomas, necesitaba hacer algunos cambios en mi compilación y diseño de 11ty. En primer lugar, agregué un nuevo campo al front matter que indica en qué idioma está la publicación. Luego necesitaba cambiar cómo mi colección de publicaciones estaba obteniendo las publicaciones del blog.
Agregué un filtro para que solo obtenga las publicaciones en inglés para no obtener todas las publicaciones en la colección.
export default (coll) => {
const posts = coll
.getFilteredByGlob("src/posts/*.md")
.filter((p) => (p.data.lang || "en") === "en");
return posts.reverse();
};
Luego, necesitaba localizar todas las traducciones para una publicación. Usé el slug del archivo como clave para crear esta colección.
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;
});
Con esto en su lugar, pude crear e incluir un indicador de idioma que agregaría banderas indicando traducciones a la cuadrícula de publicaciones. Donde cada bandera es clicable y te llevaría directamente a la publicación traducida.
{% 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' %}
{# Solo mostrar el indicador de idioma para publicaciones (no charlas o enlaces) #}
{% 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="Idiomas disponibles">
<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 %}
Resultando en:
Finalmente, agregué un menú desplegable al diseño de la publicación real junto con un descargo de responsabilidad de que la publicación fue traducida por una IA.
Con estos cambios, puedes seleccionar en qué idioma te gustaría leer cada publicación.
Conclusión
Agregar traducción automatizada a mi blog se sintió como el siguiente paso para hacer que mi contenido esté disponible para todos. Al aprovechar Amazon Nova Pro y mi arquitectura sin servidor existente, he creado una solución de traducción escalable sin agregar complejidad operativa. Ya sea que estés construyendo un blog técnico, documentación o cualquier plataforma de contenido, la traducción automatizada podría expandir tu alcance.
¿Por qué no traduje todo el sitio? Esta fue una ruta que exploré primero, pero sentí que el principal beneficio sería tener las publicaciones traducidas. También me da la libertad de decidir si me gustaría traducir todas las publicaciones más antiguas o no, y puedo agregar traducciones después. Un día podría traducir todo, pero ahora mismo el enfoque estaba en las publicaciones individualmente.
Palabras Finales
No olvides seguirme en LinkedIn y para más contenido, lee el resto de mis Blogs.
Como dice Werner: ¡Ahora Ve y Construye!
Esta publicación fue escrita originalmente en inglés y traducida automáticamente a múltiples idiomas utilizando el mismo sistema descrito aquí. El proceso de traducción tomó menos de 5 minutos y no requirió ninguna intervención manual.