Die Lingo.dev CLI übersetzt Rails-config/locales-YAML-Dateien über eine konfigurierte Lokalisierungs-Engine. Rails bringt die i18n API bereits mit – der übersetzbare Text deiner App liegt in YAML-Dateien pro Sprache. Lingo.dev passt in deine bestehende Pipeline, ohne zusätzliche Runtime-Abhängigkeiten einzuführen.
Dieser Leitfaden zeigt dir die Lokalisierung einer Rails-App von Anfang bis Ende: von der Einrichtung der CLI über die Organisation von YAML-Dateien pro Sprache und das Umschalten der Sprache pro Request bis hin zur Automatisierung von Übersetzungen in CI.
Demo-Repository
Clone oder fork lingodotdev/ruby-on-rails-localization-example, wenn du direkt mitmachen willst. Das Repo enthält eine funktionierende Rails-App mit config/locales-YAML-Dateien, einer eingecheckten .lingo/config.json und bereits vorhandenen Übersetzungen, sodass du die Konfiguration direkt neben der erzeugten Ausgabe nachvollziehen kannst.
So funktioniert Lokalisierung in Rails#
Rails liest Übersetzungen aus YAML-Dateien unter config/locales/. Jede Datei hat auf Root-Ebene einen Sprachcode und enthält verschachtelte Schlüssel, die die Lookup-Pfade widerspiegeln, die dein Code mit I18n.t verwendet.
| Ebene | Was dort liegt | Beispieldatei |
|---|---|---|
| UI-Texte | Buttons, Labels, Flash-Meldungen | config/locales/en.yml |
| Mailer-Texte | Betreffzeilen und Inhalte für ActionMailer | config/locales/mailers.en.yml |
| Modellfehler | Validierungsmeldungen und Attributnamen | config/locales/activerecord.en.yml |
Der erste Schlüssel in jeder Rails-YAML-Datei ist der Sprachcode selbst – en:, es:, fr:. Rails ordnet Übersetzungen diesem Root-Key zu, nicht dem Dateinamen: Es lädt jede Datei unter config/locales/ und speichert ihren Inhalt unter dem jeweils deklarierten Root. Eine es.yml, die weiterhin unter en: verwurzelt ist, wird also nicht ignoriert – sie wird in den en-Namespace gemergt. Für Spanisch gibt es damit am Ende gar keine Übersetzungen, und die englischen werden unbemerkt überschrieben.
Dieses Format zu übersetzen heißt daher: nicht nur die Werte, sondern auch diesen Schlüssel umzuschreiben. Das yaml-root-key-Format macht genau das: Es durchläuft den Baum unterhalb des Root-Keys, übersetzt nur String-Werte und schreibt die Zieldatei mit der Zielsprache als Root. Verschachtelte Schlüssel, %{name}-Interpolationstokens und CLDR-Pluralkategorien (zero/one/two/few/many/other) gehören zur Struktur und bleiben deshalb unverändert – genauso wie Kommentare und YAML-Anker.
Voraussetzungen#
Lokalisierungs-Engine erstellen
Bei jedem CLI-Durchlauf werden Inhalte über eine Lokalisierungs-Engine verarbeitet – also die Konfiguration, die festlegt, welches LLM-Modell, Glossar, welche Markenstimme und welche Regeln angewendet werden. Erstelle sie im Lingo.dev dashboard und generiere einen API key.
Ruby und Rails prüfen
Diese Anleitung richtet sich an Rails 7.2 oder neuer, dafür ist Ruby 3.1 oder neuer erforderlich. Prüfe deine Versionen:
ruby -v
rails -vNode.js prüfen
Die CLI setzt Node.js 22 oder höher voraus:
node -vRails i18n einrichten
Diese Anleitung setzt voraus, dass deine App Übersetzungen bereits in config/locales/*.yml speichert. Wenn du noch hartcodierte Strings in Views oder Controllern hast, extrahiere sie zuerst in t()-Aufrufe. Ersetze zum Beispiel:
<h1>Welcome</h1>durch:
<h1><%= t(".welcome") %></h1>und ergänze anschließend den passenden Schlüssel in config/locales/en.yml. Die vollständigen Migrationsschritte findest du im Rails-Leitfaden zur Internationalisierung.
Übersetzungsdateien organisieren#
Rails lädt jede *.yml-Datei unter config/locales/ automatisch. Lege die Quellsprache direkt neben die übersetzten Varianten, damit das Verzeichnis als zentrale Single Source of Truth dient:
config/locales/
en.yml # Source locale
es.yml # Generated by Lingo.dev
fr.yml
de.ymlEine typische en.yml enthält eine Mischung aus einfachen Strings, verschachtelten Namespaces, %{name}-Interpolation und Pluralisierung:
en:
hello: "Hello"
home:
welcome: "Welcome, %{name}!"
cta: "Get started"
notifications:
unread:
zero: "No unread notifications"
one: "1 unread notification"
other: "%{count} unread notifications"
errors:
messages:
blank: "can't be blank"CLI konfigurieren#
Installiere die CLI, authentifiziere dich und verknüpfe das Projekt mit deiner Engine:
npm install -g @lingo.dev/cli
lingo login
lingo init
lingo linklingo init und lingo link erstellen .lingo/config.json mit deinen Sprachen sowie der orgId und dem engineId deiner Engine. Committe die Datei zusammen mit .lingo/lock.json, damit auf jedem Rechner und in jedem CI-Lauf derselbe Stand verwendet wird.
Lass einen files[]-Eintrag auf die Quelldatei der Ausgangssprache zeigen und setze das Format explizit:
{
"orgId": "org_...",
"engineId": "eng_...",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de"],
"files": [{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" }]
}"format": "yaml-root-key" ist hier Pflicht, nicht optional. An einem .yml-Pfad lässt sich nicht erkennen, ob sein Root-Key eine Sprache oder gewöhnliche Konfiguration ist, deshalb versucht die CLI gar nicht erst zu raten: Wenn du format weglässt, wird die Datei als generisches yaml behandelt, übersetzt also die Werte, lässt den Root aber auf en: – genau der stille Fehler von oben.
Die Zieldateien werden aus dem Quellpfad abgeleitet, daher erzeugt config/locales/en.yml config/locales/es.yml, config/locales/fr.yml und config/locales/de.yml.
Rails lädt neben en.yml auch Dateien nach Zuständigkeitsbereich – devise.en.yml, mailers.en.yml, activerecord.en.yml. Füge einen zweiten Eintrag mit einem Glob hinzu, um sie mit abzudecken:
{
"files": [
{ "pattern": "config/locales/en.yml", "format": "yaml-root-key" },
{ "pattern": "config/locales/*.en.yml", "format": "yaml-root-key" }
]
}Die beiden Muster überschneiden sich nicht: Das erste matcht nur en.yml, und das zweite nur Dateien, die auf .en.yml enden. Bereits übersetzte Dateien wie es.yml und devise.es.yml werden also nie als Quellen erfasst. devise.en.yml erzeugt devise.es.yml.
Rails für mehrere Sprachen konfigurieren#
Teile Rails mit, welche Sprachen verfügbar sind und welche standardmäßig verwendet werden soll. In config/application.rb:
module YourApp
class Application < Rails::Application
config.i18n.available_locales = [:en, :es, :fr, :de]
config.i18n.default_locale = :en
config.i18n.fallbacks = [:en]
end
endLege die Sprache pro Request in ApplicationController über einen URL-Parameter oder den Header Accept-Language fest:
class ApplicationController < ActionController::Base
around_action :switch_locale
private
def switch_locale(&action)
locale = params[:locale] || http_accept_locale || I18n.default_locale
I18n.with_locale(locale, &action)
end
def http_accept_locale
header = request.headers["Accept-Language"].to_s
header.scan(/[a-z]{2}/).find { |l| I18n.available_locales.map(&:to_s).include?(l) }
end
def default_url_options
{ locale: I18n.locale }
end
endÜbersetzungen in Views ausgeben#
Verwende die Helper t und l in ERB-Templates. Ein führender Punkt im Schlüssel wird relativ zum aktuellen View-Pfad aufgelöst, sodass die Übersetzungsschlüssel direkt bei den Templates bleiben, die sie verwenden:
<h1><%= t(".welcome", name: @user_name) %></h1>
<p><%= t("notifications.unread", count: @unread_count) %></p>
<%= link_to t(".cta"), signup_path, class: "btn-primary" %>Füge deinem Layout einen Sprachumschalter hinzu:
<nav>
<% I18n.available_locales.each do |locale| %>
<%= link_to locale.upcase, url_for(locale: locale) %>
<% end %>
</nav>Lokal übersetzen#
lingo push --waitpush lädt die Quelldateien hoch, schickt sie durch deine Lokalisierungs-Engine und schreibt die übersetzten Dateien zurück. --wait sorgt dafür, dass der Befehl blockierend bleibt, bis die Ausgaben vorliegen – ein explizites Setzen lohnt sich, weil eine bevorstehende Änderung den Standard so anpasst, dass push den Lauf nur abschickt und sofort zurückkehrt, sodass du die Ergebnisse anschließend mit lingo pull abholen musst.
Spätere Läufe übersetzen nur noch das Delta: push hasht die Quelle und vergleicht sie mit .lingo/lock.json, sodass unveränderte Einträge nichts kosten. Der erste Lauf in einem Projekt – oder nach dem Hinzufügen einer Sprache – braucht den gesamten Bestand:
lingo push --backfill-missingBeschränke einen Lauf auf einen Teil der Dateien, indem du einen Glob übergibst:
lingo push "config/locales/**"Um Ausgaben abzurufen, die anderswo erzeugt wurden – auf einem anderen Rechner oder in CI –, führe lingo pull aus.
Starte den Rails-Server nach dem ersten Übersetzungslauf neu, damit die neuen YAML-Dateien geladen werden:
bin/rails serverÖffne /es, um die spanische Ausgabe zu sehen.
Pluralformen#
Rails verwendet CLDR-Pluralkategorien – zero, one, two, few, many, other. Übergib ein Argument count: an I18n.t, und Rails wählt automatisch den passenden Schlüssel:
t("notifications.unread", count: 0) # => "No unread notifications"
t("notifications.unread", count: 1) # => "1 unread notification"
t("notifications.unread", count: 12) # => "12 unread notifications"Die CLI übersetzt jede Pluralvariante direkt an Ort und Stelle. Wenn deine Zielsprache mehr Kategorien als die englischen one/other braucht, definiere sie in deinem Quell-en.yml.
In CI automatisieren#
Die Lingo.dev GitHub App übersetzt bei jedem Push und Pull Request serverseitig – ohne Runner und ohne API-Key in deinem Repository. Die Engine ermittelt sie aus der eingecheckten .lingo/config.json, daher müssen orgId und engineId in der Datei vorhanden sein, die du committest.
Wenn du die CLI lieber in deiner eigenen Pipeline ausführen möchtest – GitHub Actions, GitLab CI, Bitbucket Pipelines –, wirf einen Blick auf CI/CD Workflows. Stelle LINGO_API_KEY als Secret bereit und rufe lingo push --wait wie jeden anderen Build-Schritt auf.
Vor dem Deployment prüfen#
Verwende lingo check als Deployment-Gate, damit keine unübersetzten Inhalte in Produktion gelangen. Der Befehl endet mit einem Status ungleich null, wenn noch Einträge übersetzt werden müssen, und schreibt nichts:
lingo checkFüge es als separaten CI-Schritt vor dem Asset-Precompile oder dem Container-Build hinzu:
- name: Verify translations
run: lingo check
- name: Precompile assets
run: bundle exec rails assets:precompile