Skip to content

Commit 0dac56f

Browse files
committed
feat: ai translation workflow article
1 parent 2918ab5 commit 0dac56f

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
+++
2+
title = "Creating an AI-Powered Document Translation Workflow"
3+
description = "Outlining the translation workflow of my app, which crawls web content, translates it and prepares it for consumption through a web-based Markdown Viewer."
4+
date = "2025-04-19"
5+
+++
6+
7+
## Introduction
8+
9+
After a 3-year break from learning Spanish, I decided to find a fun and efficient way to reconnect with the language -- creating an [AI document translation app](https://github.com/dhruv-ahuja/py_ai_translator) that generates bilingual documents, readable from my Obsidian-inspired Markdown Viewer web app.
10+
Both applications run on my VPS, the Markdown Viewer being publicly exposed via Caddy.
11+
12+
## The Translation Structure
13+
14+
The following is the output document’s translation structure:
15+
16+
- Spanish text
17+
- original English text
18+
- Spanish text
19+
- original English text
20+
and so on until the end of the text
21+
22+
The structure ensures that I am able to maintain a frame of reference to the original tone and language while I reading the Spanish text. This structure allows the formation of reference points between the flow of the Spanish text versus the English written language, and helps grasp tone and meaning effectively.
23+
I’m certain there are better ways to learn the language but this has been quite interesting thus far.
24+
While I’m sure this is not the most efficient way to learn the language, I’ve found this approach to be quite engaging. The fact that I’m learning through something I built also gives me motivation.
25+
26+
## The Translation Workflow
27+
28+
The app has Docker images for web API and CLI versions, though I primarily use the CLI version.
29+
There are only three inputs:
30+
31+
- `url` of the webpage to be translated (mandatory),
32+
- `name` defines the name to save the document by, defaulting to the OpenGraph page title if none is provided, and
33+
- `cache` defines whether the crawler should use use cached webpage data for the given URL, defaulting to `True`.
34+
35+
The web crawler extracts the webpage content, which the LLM then translates using the above structure, and the resultant document is saved to the output directory. Crawled data and its translation are also persisted in a Postgres database.
36+
37+
[You can check the core application logic here](https://github.com/dhruv-ahuja/py_ai_translator/blob/main/app/utils).
38+
39+
### Web Crawler Configuration
40+
41+
`Crawl4AI` is my web crawler of choice, it crawls the given URL, prunes the content and returns a clean Markdown result. Although its dependencies increase the Docker image size, the trade-off is acceptable since the crawler runs locally, and instantiates a headless browser session. It also caches previously crawled pages, fetching content instantly for repeated URLs.
42+
43+
_Sample `Crawl4AI` output_
44+
45+
```bash
46+
[INIT].... → Crawl4AI 0.5.0.post4
47+
[FETCH]... ↓ https://example.com... | Status: True | Time: 1.22s
48+
[SCRAPE].. ◆ https://example.com... | Time: 0.008s
49+
[COMPLETE] ● https://example.com... | Status: True | Total: 1.23s
50+
```
51+
52+
### Translating the Crawled Data
53+
54+
The choice of [OpenRouter](https://openrouter.ai) as the LLM provider unlocks access to hundreds of LLM models with a single API key. This allowed for rapid experimentation to find the right model for my needs.
55+
I currently use Google’s Gemini 2.0 Flash-Lite for its affordability and speed, while handling effective translation and extraction of relevant content from the crawled text body.
56+
57+
It can however, occasionally fail to structure the translated content under headings properly, moving the text body under the English heading instead.
58+
This hasn’t bothered me in practice, and I believe this can be corrected with further tweaking of the LLM prompts.
59+
60+
### Brief on AI Usage and Observability
61+
62+
`PydanticAI` was quick to setup and use with OpenRouter for my straightforward use case, and it is apparently easy to configure for more complex tasks in the future. Integrating `Logfire` with PydanticAI for agent observability was as easy as passing a boolean parameter to the `Agent` object instance. Logfire itself requires just a few lines of code and an API key acquired from its web dashboard.
63+
64+
Observability data will help keep track of draw comparisons between usage costs of various models, when translating particular documents.
65+
66+
![_Token usage and cost for a long, text-only article, observed in Pydantic Logfire_](/images/ai_translation/logfire.png)
67+
68+
### Consuming the Output
69+
70+
I trigger the application using Docker as a removable container, using something like
71+
72+
```bash
73+
docker run --env-file .env.deploy --network app-network --rm dhruvahuja/py_ai_translator_cli -v ~/docker_data/public_markdown:/app/markdown 'https://www.example.com'
74+
```
75+
76+
The `app-network` Docker network connects the app to a shared Postgres instance, which stores crawled content, translated outputs, and metadata for future reference.
77+
A simple [Markdown Viewer Node webapp](https://feed.dhruvahuja.me/files) scans the output directory and lists all translated documents along with `#public` tagged notes, making them accessible via a clean web UI.
78+
79+
![_Sample document output in the Markdown Viewer app, where `example.md` is the translated document_](/images/ai_translation/markdown_viewer.png)
80+
81+
## Conclusion: AI For the Win
82+
83+
I have enjoyed working on the application and the surrounding tooling that I’ve built to accomplish my goals. Having AI services such as v0.dev is a godsend for creating designs and POCs, like the Markdown Viewer that emulates Obsidian’s look-and-feel. AI is enabling us to build at a breath-taking pace, with increased emphasis on the desired outcome rather than the boilerplate and particulars of a language.
84+
85+
In this case, I did not have to learn Node syntax to prepare a functional script, while acutely reviewing and understanding the rationale behind the code. I similarly wrote a toy Go app to backup my VPS’ crucial data to S3 with certain conditions, in a couple hours.
86+
87+
Finally, I’ll be relearning Spanish through content I enjoy. I am deferring improvements to the code or the features, as it’s time to actually use I’ve built!

0 commit comments

Comments
 (0)