<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arvid Andersson</title>
    <description>The latest articles on DEV Community by Arvid Andersson (@arvid_andersson_0a598fa45).</description>
    <link>https://dev.to/arvid_andersson_0a598fa45</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1939937%2Fff79f2f3-1b43-49fd-a66c-ebcda9008a94.jpg</url>
      <title>DEV Community: Arvid Andersson</title>
      <link>https://dev.to/arvid_andersson_0a598fa45</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arvid_andersson_0a598fa45"/>
    <language>en</language>
    <item>
      <title>Translate i18n strings in CI with GitHub Actions: tools compared</title>
      <dc:creator>Arvid Andersson</dc:creator>
      <pubDate>Sun, 14 Jun 2026 22:04:04 +0000</pubDate>
      <link>https://dev.to/arvid_andersson_0a598fa45/translate-i18n-strings-in-ci-with-github-actions-tools-compared-1i8g</link>
      <guid>https://dev.to/arvid_andersson_0a598fa45/translate-i18n-strings-in-ci-with-github-actions-tools-compared-1i8g</guid>
      <description>&lt;p&gt;If your team ships a localized product continuously, translations have a way of becoming the step everything waits on. The feature is done, the PR is green, and then it sits because the German and Japanese copy is blank. The fix a lot of teams reach for is to move translation into CI: when a PR changes the source locale, something translates the new keys and commits them back before merge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4q9cjv97un6fvfoyj6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4q9cjv97un6fvfoyj6h.png" alt="Developer opens a PR, translations generate in CI, the branch merges and deploys, and the product shows up localized" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That loop, source PR in, localized product out, is the whole goal. The rest is which tool runs the middle step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A quick summary of the options:&lt;/strong&gt; the tools worth knowing for this are &lt;a href="https://github.com/i18n-actions/ai-i18n" rel="noopener noreferrer"&gt;ai-i18n&lt;/a&gt; if you want open source and don't mind wiring it up; hosted actions like &lt;a href="https://lingo.dev" rel="noopener noreferrer"&gt;Lingo.dev&lt;/a&gt; for a quick CI hook; the big TMS platforms (&lt;a href="https://crowdin.com" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt;, &lt;a href="https://lokalise.com" rel="noopener noreferrer"&gt;Lokalise&lt;/a&gt;, &lt;a href="https://www.transifex.com" rel="noopener noreferrer"&gt;Transifex&lt;/a&gt;) if you have a localization team; and &lt;a href="https://localhero.ai" rel="noopener noreferrer"&gt;Localhero.ai&lt;/a&gt; (ours), built around the translate-on-PR workflow with glossary consistency and a review UI for non-developers. What separates them isn't the API call. It's how consistent the output stays over time and whether a non-developer can review it.&lt;/p&gt;

&lt;p&gt;One thing to keep in mind while you compare: getting an LLM to translate one string is easy. Getting &lt;em&gt;consistent, on-brand&lt;/em&gt; translations across hundreds of keys and many languages, PR after PR, so "Workspace" is the same word in German every time and the tone doesn't drift, is not. And whatever you pick, someone usually still wants to review the copy that matters before it ships.&lt;/p&gt;

&lt;p&gt;There are quite a few ways to run that step now, from open-source GitHub Actions you wire up yourself to hosted services that run as an Action. Before the tools themselves, here's what actually separates them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What "good" looks like for CI translation
&lt;/h3&gt;

&lt;p&gt;Most of these tools look similar in a demo. The differences show up a few months in, on a real product with real churn. The things that matter by then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delta translation.&lt;/strong&gt; Translate only the keys that changed in the PR, not the whole file every time. It keeps CI fast and gives the model tighter context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glossary and tone consistency.&lt;/strong&gt; "Workspace" should be translated the same way in PR #200 as it was in PR #5. It helps when a tool remembers the edits you've made and picks up the patterns already in your codebase, so corrections stick instead of getting re-litigated every PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A review surface.&lt;/strong&gt; A YAML diff is not a place a PM or native speaker can sanity-check copy. Either you trust the output fully, or you need somewhere to review it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit-back vs PR.&lt;/strong&gt; Committing to the same PR is convenient; opening a separate translation PR is safer for some teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  i18n translation tools that run as a GitHub Action
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/i18n-actions/ai-i18n" rel="noopener noreferrer"&gt;ai-i18n&lt;/a&gt; (open source GitHub Action).&lt;/strong&gt; A solid choice if you want full control and don't mind wiring it up. It translates i18n files with an LLM provider you choose (Anthropic, OpenAI, or self-hosted Ollama), uses content hashing to translate only changed strings, and commits results back. It handles XLIFF and JSON (flat and nested). You own the config and the maintenance. Best when you want to tune everything yourself and keep the model choice in your hands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosted CI-action tools (Lingo.dev and a few others).&lt;/strong&gt; A handful of hosted services run their own GitHub Action that translates on the delta and commits back. &lt;a href="https://lingo.dev" rel="noopener noreferrer"&gt;Lingo.dev&lt;/a&gt; is one of the more visible ones. They cover the basic translate-in-CI loop across a range of CI providers. Where they differ from each other (and from the option below) is less the CI mechanics and more what happens around the strings: how consistent terminology stays over time, and whether a non-developer can review the output. Worth a look if you mainly want the CI hook itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://crowdin.com" rel="noopener noreferrer"&gt;Crowdin&lt;/a&gt; / &lt;a href="https://lokalise.com" rel="noopener noreferrer"&gt;Lokalise&lt;/a&gt; / &lt;a href="https://www.transifex.com" rel="noopener noreferrer"&gt;Transifex&lt;/a&gt; (TMS with GitHub sync).&lt;/strong&gt; The established translation management systems. They sync with GitHub (Crowdin opens a PR when translations update) and are built for teams with dedicated translators: assigning work, tracking who translated what, managing big translation projects. Crowdin has documented support for both react-i18next and LinguiJS. If you have a localization team and a lot of long-form content, this is the category for you. If you're a product team that just wants UI strings translated on every PR, it's usually more platform than the job needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://tolgee.io" rel="noopener noreferrer"&gt;Tolgee&lt;/a&gt;.&lt;/strong&gt; Open source with a hosted tier, an in-context editor, and CI integration. Supports the major JS frameworks (React, Vue, Angular, Svelte, Next) plus JSON and PHP &lt;code&gt;.po&lt;/code&gt;. Good middle ground if you want an open-source core plus a UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://localhero.ai/" rel="noopener noreferrer"&gt;Localhero.ai&lt;/a&gt; (ours).&lt;/strong&gt; Built for product teams that ship continuously. It runs as a GitHub Action, translates the changed i18n keys on each PR, and focuses on the two things that make this hold up on a real product: a glossary and brand voice that stay consistent across PRs via translation memory, and a review UI a PM or native speaker can use instead of reading a YAML diff. Works with react-i18next, LinguiJS, Rails (YAML), and Django (&lt;code&gt;.po&lt;/code&gt;). We're also adding review for translations a PR already includes, not just the ones we generate, so teams bringing their own translations get the same review surface. It's narrower than a full platform on purpose. If you want a focused tool for the translate-on-PR workflow rather than a large platform to configure, it's worth a look. If you want to self-host or stay fully open source, ai-i18n or Tolgee fit better.&lt;/p&gt;

&lt;h3&gt;
  
  
  A rough decision guide
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Want full control, open source, tune it yourself:&lt;/strong&gt; ai-i18n or Tolgee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have a dedicated localization team and long-form content:&lt;/strong&gt; Crowdin / Lokalise / Transifex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A product team that ships continuously and wants UI strings translated per-PR, with glossary/brand-voice consistency and a review surface for non-devs:&lt;/strong&gt; try Localhero.ai.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The pattern, regardless of tool
&lt;/h3&gt;

&lt;p&gt;Whatever you pick, the setup that holds up is the same: translate only changed keys, protect placeholders before sending to the model, pass the glossary in context so terms don't drift, validate output before committing, and give someone a place to review brand-sensitive copy. The tool matters less than getting those pieces right.&lt;/p&gt;

&lt;p&gt;The DIY route is genuinely viable, and we wrote up that pattern in detail &lt;a href="https://localhero.ai/blog/translate-json-yaml-in-ci" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The cost that's easy to undercount isn't the first build, it's the upkeep: the placeholder edge case that shows up six months later, the glossary logic, the model swap when prices change, the review surface someone eventually asks for. That work competes with your actual product for engineering time and attention. For some teams that ownership is worth it. For others, the reason to use a tool here is less the features and more not carrying that maintenance yourself.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automating Translations With Your Coding Agent</title>
      <dc:creator>Arvid Andersson</dc:creator>
      <pubDate>Wed, 08 Apr 2026 13:08:49 +0000</pubDate>
      <link>https://dev.to/arvid_andersson_0a598fa45/automating-translations-with-your-coding-agent-23la</link>
      <guid>https://dev.to/arvid_andersson_0a598fa45/automating-translations-with-your-coding-agent-23la</guid>
      <description>&lt;p&gt;If you've been building features with Claude Code or Cursor, you know the feeling. You're in flow, the agent is writing components, wiring up routes, adding tests. Then you hit the translation strings. What's the German word for "workspace" that we should use? Is it "Arbeitsbereich" or did we decide to keep it as "Workspace"? Who should review this?&lt;/p&gt;

&lt;p&gt;And even when you get the source strings right, the translations themselves are often a separate step. Someone coordinates them, someone else reviews them, the feature sits in a PR waiting. With AI coding agents speeding up development, this gap only gets wider.&lt;/p&gt;

&lt;p&gt;Been working in this space for a while, and the thing that made the biggest difference was simple: just take the translation knowledge your team already has collectively, make it structured, and ensure your coding agent has access to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem isn't translating, it's consistency and brand voice
&lt;/h2&gt;

&lt;p&gt;AI can translate. That's not the hard part any more. The hard part is getting translations that sound like your product, consistently, when multiple people and agents are all contributing to the same codebase.&lt;/p&gt;

&lt;p&gt;Think about what happens without a shared glossary and style guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developer A's agent translates "Workspace" as "Arbeitsbereich", Developer B's agent keeps it as "Workspace"&lt;/li&gt;
&lt;li&gt;One PR uses formal German ("Sie"), another uses informal ("du")&lt;/li&gt;
&lt;li&gt;A contractor pastes in ChatGPT translations with a completely different tone&lt;/li&gt;
&lt;li&gt;Your native speaker on the team corrects the same term for the third time this month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app ends up feeling like it was translated by five different people, because it was. Your brand voice gets lost the moment it crosses a language boundary. And every new language multiplies the problem.&lt;/p&gt;

&lt;p&gt;This is why a glossary and style guide matter more than the translation engine. They're the single source of truth that keeps your brand voice consistent regardless of who or what writes the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a glossary and style guide
&lt;/h2&gt;

&lt;p&gt;Before you automate anything, sit down with and make some decisions. These are the things that no one, not your coding agent, not a new team member, not a translation tool, can figure out on their own easily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which terms should never be translated?&lt;/strong&gt; Product names, feature names, technical terms your users know in English. "Workspace", "Dashboard", "API" might all stay as-is in German.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which terms have a specific translation?&lt;/strong&gt; Maybe "Save" is always "Speichern", not "Sichern". Maybe "team member" becomes "Teammitglied", not "Mitarbeiter". These are the terms your native speakers have opinions about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's the tone?&lt;/strong&gt; Formal or informal? In German that's the difference between "Sie" and "du", in French between "vous" and "tu". This needs to be consistent across your whole app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any language-specific decisions?&lt;/strong&gt; Scandinavian languages often sound better with a natural spoken register rather than formal written style. Japanese needs the right politeness level. These are things worth writing down once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a conversation with your PM, your content person, the native speakers on your team. It doesn't take long, but the decisions need to be explicit, not just in someone's head. People usually have opinions and examples ready once you ask.&lt;/p&gt;

&lt;p&gt;Once you have this in place, document it. Agents or tools that use it will produce translations that actually sound like your product. Without it, even the best AI will just guess differently every time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hkevgtddcdzsgla3dw8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hkevgtddcdzsgla3dw8.png" alt="Translations committed automatically to the PR by a GitHub Action" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A nice thing this opens up for is automating. The actual translations into target languages can then happen in CI. For example, a GitHub Action picks up new strings on the PR, translates them with glossary enforcement, and commits them back. By the time someone reviews, all languages are there. Think of it like linting or tests, but for translations.&lt;/p&gt;

&lt;h2&gt;
  
  
  You could wire this up yourself
&lt;/h2&gt;

&lt;p&gt;And for a small project with a few languages, you should. Call an LLM API, write a validation script, keep a glossary file in your repo. There are open source tools like &lt;a href="https://github.com/nicekiwi/i18n-ai-translate" rel="noopener noreferrer"&gt;i18n-ai-translate&lt;/a&gt; and &lt;a href="https://github.com/fkirc/attranslate" rel="noopener noreferrer"&gt;attranslate&lt;/a&gt; that let you bring your own API key and run translations locally. Good starting points.&lt;/p&gt;

&lt;p&gt;But the translation itself is only one piece. The harder parts come later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency over time&lt;/strong&gt;: How do you make sure translations stay consistent as your glossary evolves and new people join the team?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality validation&lt;/strong&gt;: Who catches broken placeholders, wrong formality levels, glossary violations, or tone drift?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compound learning&lt;/strong&gt;: When someone on your team corrects a translation, does that correction inform future translations?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-technical review&lt;/strong&gt;: Can your PM or content person review and edit translations without opening a JSON file?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeping it running&lt;/strong&gt;: You built it, now you maintain it. Every edge case, every new language, every LLM API change is on your team. That's time spent on translation infrastructure instead of your product.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'm building to solve this
&lt;/h2&gt;

&lt;p&gt;It's not worth rebuilding this infrastructure for every project. &lt;a href="https://localhero.ai" rel="noopener noreferrer"&gt;Localhero.ai&lt;/a&gt; is what I decided to build, a translation service for real product teams, not translators. The core idea is that everything compounds: every translation you ship, every correction someone makes, every glossary term you add feeds into making the next translation better.&lt;/p&gt;

&lt;p&gt;One cool part is how it connects to coding agents. There's an &lt;a href="https://localhero.ai/docs/ai-agents" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; that loads your glossary, tone settings, and naming conventions dynamically every time your agent works on translation-related code. Install it with &lt;code&gt;npx skills add localheroai/agent-skill&lt;/code&gt; and it works with Claude Code, Cursor, and any agent that reads &lt;a href="https://skills.sh" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt; skill files. If your PM adds a new term to the glossary or adjusts the style guide, every developer's agent picks that up on the next task. No syncing, no Slack messages.&lt;/p&gt;

&lt;p&gt;On the CI side, a GitHub Action ensures everything is in sync. Every PR that touches locale files gets translated automatically with glossary enforcement, translation memory, and language-specific rules for things like German formality or Scandinavian natural register. Just write the source language and let CI handle the localization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy638e2hrjemgurm4jndk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy638e2hrjemgurm4jndk.png" alt="Anyone on the team can review and edit translations without touching code" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a lot more going on under the hood than just calling an LLM. Quality checks catch broken placeholders, glossary violations, and tone drift before anything lands in your PR. Every PR gets a dedicated review page where anyone on the team can edit translations inline or apply suggested fixes. And when someone corrects a translation, that decision feeds back into translation memory, so the system learns from your team's choices over time. It's like working with a translator that actually remembers what you decided last month.&lt;/p&gt;

&lt;p&gt;It works with JSON (React, Next.js, Vue), YAML (Rails), and PO files (Django, Python). The &lt;a href="https://localhero.ai/docs/ai-agents" rel="noopener noreferrer"&gt;agent skill&lt;/a&gt; and &lt;a href="https://github.com/localheroai/cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; are open source, and there's a &lt;a href="https://localhero.ai/pricing" rel="noopener noreferrer"&gt;free plan&lt;/a&gt; if you want to try it on a real project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compounding part
&lt;/h2&gt;

&lt;p&gt;The thing I like most about this setup is that it gets better on its own. Every translation you ship, every correction your team makes, every glossary term you add feeds forward. Six months in, the system knows your voice better than any new hire would. That's the part you can't get from a script and an API call.&lt;/p&gt;

&lt;p&gt;If you're shipping in multiple languages and it still feels like a separate workstream, start with the glossary. Get your team's decisions documented, give that context to your agent, and see what happens. And if you want help setting it up, reach out, happy to help.&lt;/p&gt;

</description>
      <category>i18n</category>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
