
Rails har ett inbyggt ramverk för internationalisering. Du behöver inte mycket mer för att komma igång – I18n-API:et ingår i stacken och hanterar grunderna på ett bra sätt: översättning av statiska strängar, formatering av datum och siffror samt hantering av pluralformer i olika språkversioner.
Men ”inbyggt” betyder inte ”komplett”. Så fort dina krav omfattar lokaliserade webbadresser, översättningar i JavaScript eller flerspråkig SEO, befinner du dig utanför det som Rails erbjuder som standard. Dessa luckor kräver väl genomtänkta arkitekturbeslut.
Låt oss gå igenom båda nivåerna: vad Rails I18n-API hanterar, och var du behöver bygga själv – eller delegera.
I18n-API:et erbjuder två huvudmetoder: I18n.t för översättning av strängar och I18n.l för lokalisering av datum och tal. Båda läser in data från YAML- eller Ruby-filer i mappen config/locales/, och Rails laddar dem automatiskt vid uppstart.
Som standard omfattar detta statiska UI-strängar, formatering av datum och siffror samt valideringsmeddelanden i ActiveRecord. För de flesta projekt där man översätter från ett språk till ett annat räcker det för att släppa produkten.
Bristerna blir snabbt uppenbara när man går djupare in i ämnet.
Rails I18n har inga krav på URL-strukturen: inga lokaliserade rutter, ingen detektering av underdomäner och inga sökvägsprefix. Det finns ingen funktion för att översätta innehåll som lagras i databasen, såsom produktnamn eller blogginlägg. JavaScript-filer ligger helt utanför pipelinen. Och flerspråkig SEO – till exempel hreflang-taggar, översatta slugs och språkspecifika webbplatskartor – kräver separat arbete som ramverket inte hanterar.
Du måste veta var gränsen går innan du börjar bygga.
Du kan börja med gemet rails-i18n.
Rails levererar endast språkinställningsdata för engelska, så utan dessa misslyckas I18n.l(Date.today) i andra språkinställningar än engelska, och strängar på ramverksnivå, såsom valideringsmeddelanden i Active Record, förblir på engelska oavsett aktuell språkinställning.
# Gemfile
gem "rails-i18n"Konfigurera sedan standardinställningarna för din applikation:
# config/application.rb
config.i18n.default_locale = :en
config.i18n.tillgängliga_språk = [:en, :fr]
config.i18n.enforce_available_locales = true
enforce_available_locales = true ger ett felmeddelande om din app försöker ställa in en språkinställning som inte finns med i listan. Utan detta kan ett skrivfel eller en felaktig URL-parameter i tysthet leda till att en språkinställning som inte stöds aktiveras i produktionsmiljön.
Även om moderna Rails-installationer skannar en nivå djupt, missar de ofta djupt inbäddade mappar (som config/locales/views/products/). Genom att lägga till den här raden säkerställer du att alla underkataloger inkluderas när din app växer:
config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")]På så sätt kan du dela upp översättningsfilerna efter domän:
config/locales/
en/
models.yml
views.yml
mailers.yml
fr/
modeller.yml
views.yml
mailers.yml⚠️ YAML tolkar värden som true och false som booleska värden. Om du vill använda dem som ren text bör du sätta dem inom citattecken. I nyare versioner av Ruby och YAML behandlas yes och no numera som strängar, men det är fortfarande en bra vana att sätta dem inom citattecken av kompatibilitetsskäl:
sv:
svar:
ja: "ja"
nej: "nej"Detta upptäcker en typ av fel som uppstår endast när en översättning returnerar ”true” istället för ”ja” och din vy inte visar något, eller ännu värre, strängen ”true”.
I vyerna är t och l de två hjälpfunktionerna som du kommer att använda hela tiden.
l anpassar datum, tider och siffror efter den aktuella språkinställningen. Om rails-i18n är installerat ingår formatdefinitioner för över 100 språkinställningar. Utan detta kommer ett anrop av l(Date.today) i en icke-engelsk språkinställning vanligtvis att returnera en sträng med meddelandet ”translation missing” eller ett oformaterat datum, eftersom Rails saknar de nödvändiga lokaliserade formatmallarna.
<%= l(Date.today) %>
<%= l(Date.today, format: :long) %>t översätter strängar efter nyckel. Den fullständiga formen är t("products.index.title"), men i vyer kan du använda den förkortade formen för lat uppslagning:
<%= t(".title") %>Den inledande punkten talar om för Rails att nyckeln ska tolkas i förhållande till den aktuella visningsvägen. I filen app/views/products/index.html.erb omvandlas t(".title") automatiskt till t("products.index.title"). Detta gör nycklarna korta och säkerställer en enhetlig namngivningskonvention utan extra arbete.
For interpolation, use %{variable} in your YAML and pass the value as a keyword argument:
en:
welcome: "Hello, %{name}"<%= t(".welcome", name: current_user.name) %>Om en översättning innehåller HTML som du litar på, lägg till _html i nyckelnamnet. Rails markerar den automatiskt som säker, utan att du behöver använda funktionen html_safe.
en:
notice_html: "Please <strong>confirm</strong> your email."Lazy lookup fungerar endast när Rails kan härleda sökvägen till vyn, vilket innebär att det inte fungerar i bakgrundsjobb och API-kontroller. I dessa sammanhang bör du alltid använda den fullständiga nyckeln.
Innan du börjar skriva kod för att identifiera språkinställningar bör du välja en strategi. Det finns fem huvudsakliga metoder, och vilken som är rätt beror på din apps uppbyggnad och dina SEO-krav.
Ett sökvägsprefix, till exempel /fr/products, är det vanligaste valet för Rails-applikationer som riktar sig till allmänheten. Det gör att språkinställningen framgår tydligt i varje URL, vilket gör att sökmotorerna kan indexera dem separat för varje språk.
Strategier med underdomäner och toppdomäner fungerar bra när man vill skapa en starkare regional identitet, men de medför extra arbete med DNS-konfiguration och SSL-certifikat.
URL-parametrar, som /products?locale=fr, fungerar bra för interna verktyg där sökmotoroptimering inte spelar någon roll.
Inställningar som lagras i databasen fungerar för inloggade appar där språkinställningen följer användaren, inte webbadressen.
Oavsett vilken strategi du väljer finns det ett implementeringsfel som kan orsaka svårupptäckta buggar i produktionsmiljön: att använda I18n.locale = direkt.
# Don't do this
before_action { I18n.locale = params[:locale] }I18n.locale = skriver till Thread.current.
Om du använder en Puma-webbserver återanvänder den trådar mellan olika förfrågningar. Om en förfrågan inte uttryckligen anger språkinställningen, övertar den de inställningar som den föregående förfrågan lämnade efter sig.
Vid lokal utveckling med en enda tråd märks detta aldrig. I produktionsmiljön leder det till sporadiska svar på fel språk som är svåra att återskapa och ännu svårare att spåra.
Använd istället I18n.with_locale inuti en around_action:
around_action :switch_localed
ef switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
endwith_locale återställer den tidigare språkinställningen när blocket avslutas, oavsett hur begäran avslutas.
Definiera dina rutter under /:locale och skriv över default_url_options så att Rails automatiskt lägger till den aktuella språkinställningen i början av varje URL-hjälpfunktion:
# config/routes.rb
scope "/:locale" do
resources :products
root "home#index"
end
# app/controllers/application_controller.rb
around_action :switch_locale
def switch_locale(&action)
locale = params[:locale]
# Fallback to default if the param is missing or unsupported
valid_locale = I18n.available_locales.map(&:to_s).include?(locale) ? locale : I18n.default_locale
I18n.with_locale(valid_locale, &action)
end
def default_url_options
{ locale: I18n.locale }
end
När default_url_options är inställt visas products_path automatiskt som /fr/products när den aktuella språkinställningen är :fr.
Om du vill att standardlokalen ska utelämna prefixet ska du göra segmentet valfritt med räckvidden "(:locale)". Detta ger /products för engelska och /fr/products för franska, men skapar oklarhet. En sökväg som /about kan antingen matcha en saknad lokal eller en kontroller med namnet about, beroende på hur dina rutter är ordnade.
För att identifiera underdomäner ska du läsa in värdet från `request.subdomains.first` och jämföra det med `available_locales` innan du ställer in något:
def extrahera_språkinställning_från_underdomän
subdomain = request.subdomains.first
return nil if subdomain.blank? || subdomain == "www"
subdomain om I18n.available_locales.map(&:to_s).include?(subdomain)
end
Kontrollen av www förhindrar att den behandlas som en lokalisering. På localhost returnerar request.subdomains en tom array, vilket innebär att detekteringen av underdomäner misslyckas i utvecklingsmiljön om du inte använder en Pow-server eller lägger till poster i /etc/hosts.
En sak som måste konfigureras separat: `default_url_options`, som definieras i `ApplicationController`, överförs inte till e-postmoduler eller bakgrundsjobb. Ange det uttryckligen för dessa sammanhang:
Rails.application.routes.default_url_options = { host: "example.com", locale: :en }Med lokaliserade sökvägar får du URL-strukturen, men hreflang-taggar, översatta slugs och flerspråkiga webbplatskartor måste fortfarande hanteras manuellt efter denna konfiguration.
Weglot integration med omvänd proxy hanterar detta automatiskt och genererar hreflang-taggar och språkspecifika webbadresser utan ytterligare konfiguration.
Pluralbildningen på engelska är enkel. En form för singular, en för alla andra fall.
en:
messages:
one: "%{count} message"
other: "%{count} messages"
De flesta språk är inte så enkla.
I bulgariska finns det till exempel en vanlig pluralform och en särskild ”räknande plural” för vissa ord i maskulin form. Så ден (dag) blir normalt дни (många dagar), men два дена (två dagar) när man räknar.
bg:
cities:
one: "%{count} ден"
few: "%{count} дена"
many: "%{count} дни"
Utan rails-i18n har Rails ingen kännedom om dessa regler. Dina bulgariska översättningar skulle antingen ge fel eller automatiskt byta till den andra formen vid varje räkning.
Gem levererar pluraliseringslogik för över 100 språk, vilket gör att texterna visas korrekt på bulgariska, ryska, arabiska, polska och alla andra språk med andra pluralregler än de engelska.
Reservvärden är en separat fråga och är avstängda som standard. Utan dem leder varje saknad översättningsnyckel till att en sträng visas som saknad i produktionsmiljön. Det är just den typen av problem som slinker med och syns på skärmdumpar.
Aktivera reservalternativ och ange en standarddestination i config/application.rb:
config.i18n.fallbacks = [I18n.default_locale]Om fallbacks = true används standardlokalen (default_locale) om en nyckel saknas i den aktuella lokaliseringen. För regionala varianter kan du ange uttryckliga kedjor:
config.i18n.fallbacks = { "fr-CA": :fr, "en-GB": :en }Se till att konfigurera detta i ett tidigt skede. Det är svårare att i efterhand lägga till reservlösningar i en app som redan har delvisa översättningar i drift än att ordna det från början.
JavaScript har inte tillgång till Rails I18n-pipeline.
Det finns ingen t-hjälpfunktion, ingen YAML-laddare och ingen inbyggd koppling mellan dina översättningsfiler och dina Stimulus-kontroller. Du måste skicka översättningarna explicit från serversidan.
Det enklaste sättet att använda Stimulus är via värde-API:et. Definiera översättningen som ett värde i kontrollern, ange det i HTML-koden och läs in det i JavaScript:
# app/views/products/index.html.erb
<div data-controller="notification"
data-notification-message-value="<%= t('.success_message') %>">
Värdet renderas på serversidan med hjälp av standardhjälpfunktionen `t`, vilket innebär att det automatiskt anpassas efter den aktuella språkinställningen. På JavaScript-sidan ska du deklarera det som ett statiskt värde och hämta det via values-API:
// app/javascript/controllers/notification_controller.js
export default class extends Controller {
static values = { message: String }
show() {
alert(this.messageValue)
}
}
Varje översättning är explicit och begränsad till den kontroller som behöver den. Ingenting läcker ut i det globala området.
Om en kontrollobjekt behöver flera översättningar samtidigt är det enklare att samla dem i ett JSON-dataattribut än att lägga till enskilda värdedefinitioner för varje sträng:
<div data-controller="cart"
data-cart-i18n-value="<%= { add: t('.add'), remove: t('.remove') }.to_json %>">
I JavaScript deklarerar du i18n som ett objekt och hämtar enskilda nycklar direkt:
static values = { i18n: Object }
add() {
console.log(this.i18nValue.add)
}
För appar där JavaScript behöver omfattande åtkomst till översättningsdata från flera olika kontroller exporterar gemet i18n-js dina YAML-filer till ett JavaScript-objekt som du kan använda med uttrycket I18n.t("nyckel").
Det fungerar, men det innebär ett extra steg i byggprocessen och att hela översättningsuppsättningen skickas till klienten. Använd det när de två första metoderna börjar bli repetitiva, inte som standard.
När du använder Turbo Frames och en Turbo Frames käll-URL saknar språkprefixet, kommer begäran vanligtvis att misslyckas med ett routningsfel (404), eftersom den inte stämmer överens med mönstret för ditt område "/:locale". Använd alltid språkmedvetna sökvägshjälpfunktioner för ramkällor.
Det här är inte ett fel i Turbo. Det handlar istället om ett misstag i routningen, och funktionen `around_action` i avsnittet om språkdetektering förhindrar detta så länge varje URL i ramen använder en språkmedveten sökvägshjälpfunktion.
Weglot omvända proxy fungerar på ett helt annat sätt. Den översätter det renderade DOM-objektet efter att sidan har laddats, vilket innebär att innehåll som renderats av Stimulus och dynamiskt infogade strängar hanteras utan någon sådan koppling.
Tre kategorier faller helt utanför det som Rails I18n hanterar:
Weglot hanterar alla dessa på utdatasidan snarare än på kodsidan:
Det finns dock några begränsningar som är bra att känna till innan du utvärderar det:
Varje Rails-implementering av i18n handlar i grunden om samma val, som görs i ungefär samma ordning.
Bestäm först vilken URL-strategi du ska använda innan du skriver någon kod för att identifiera språkinställningar. Sökvägssegment fungerar för de flesta appar som riktar sig till allmänheten. Underdomäner är ett bra val när den regionala identiteten är viktig. Inställningar som lagras i databasen passar för autentiserade appar där språkinställningarna följer användaren snarare än URL:en.
För det andra ska du bestämma hur JavaScript ska hämta översättningarna. Stimulus-API:et för värden täcker de flesta fall. JSON-attribut fungerar när en kontroller behöver flera strängar samtidigt.
För det tredje: bestäm vad du ska bygga manuellt. Rails hanterar statiska strängar väl. SEO-lagret, översättningsflödet och databasinnehållet kräver alla separata beslut. Du kan bygga varje del själv, eller överlåta utdataskiktet till ett verktyg som Weglot istället lägga utvecklingstiden på själva applikationen.
Hoppa över utdataskiktet helt och hållet. Testa Weglot i 14 dagar och få tillgång till flerspråkig SEO/GEO, översatta webbadresser och automatisk innehållsidentifiering utan att behöva röra din i18n-kod.
Det bästa sättet att förstå Weglot kraft Weglot att se det själv. Testa det gratis och utan förpliktelser.
En demowebbplats finns tillgänglig i din instrumentpanel om du inte är redo att ansluta din webbplats ännu.