Localization (i18n)
Avo leverages Rails' powerful I18n translations module.
Translations are used exactly as you write them
Avo renders a resolved resource or field translation verbatim. It only humanizes the name it generates for you when no translation is found. Write one: 'Usuario' and you get Usuario; write one: 'usuario' and you get usuario. This also means acronyms and proper nouns survive -- 'Payment Intent ID' stays Payment Intent ID instead of becoming Payment intent id.
Multi-language URL Support
If you're serving Avo using multiple languages and you're using the locale in your routes (/en/resources/users, /de/resources/users), check out this guide.
Avo ships its own locale files (avo.en.yml plus 18 other languages) bundled inside the gem and loads them into the I18n module automatically, so bin/rails avo:install doesn't copy them into your app. When you want editable copies under config/locales, run the locale generator.
Localizing resources
Let's say you want to localize a resource. All you need to do is add a self.translation_key class attribute in the Resource file. That will tell Avo to use that translation key to localize this resource. That will change the labels of that resource everywhere in Avo.
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
self.title = :name
self.translation_key = 'avo.resource_translations.user'
end# avo.es.yml
es:
avo:
dashboard: 'Dashboard'
# ... other translation keys
resource_translations:
user:
zero: 'Usuarios'
one: 'Usuario'
other: 'Usuarios'These values are used as written, so capitalize them the way you want them to appear in page titles, breadcrumbs, and the sidebar.
If you don't set self.translation_key, Avo derives it from the resource's class name, namespace included — a namespaced resource like Avo::Resources::Galaxy::Planet defaults to avo.resource_translations.galaxy/planet.
Localizing actions
Actions follow the same convention. Avo looks up avo.action_translations.<class_path>.{name,message,confirm_button_label,cancel_button_label,description} before falling back to the class attributes (self.name, self.message, and so on).
# app/avo/actions/toggle_inactive.rb
class Avo::Actions::ToggleInactive < Avo::BaseAction
# Optional. Defaults to avo.action_translations.toggle_inactive
# self.translation_key = "avo.action_translations.toggle_inactive"
end# avo.sv.yml
sv:
avo:
action_translations:
toggle_inactive:
name: "Växla inaktiv"
message: "Är du säker på att du vill växla inaktiv status?"
confirm_button_label: "Växla"
cancel_button_label: "Avbryt"
description: "Växlar den inaktiva statusen för användaren"
city/update:
name: "Uppdatera stad"Namespaced actions use the same underscored, slash-joined path as resources — Avo::Actions::City::Update resolves to avo.action_translations.city/update.
You can still set self.name, self.message, and the button labels as strings or lambdas. Those remain the fallback when no translation key is present, which is useful for dynamic labels that depend on arguments or record.
Localizing fields
Similarly, you can even localize fields. When you don't set an explicit translation_key: option on the field declaration, Avo resolves the field label through a cascade, using the first key that has a translation:
avo.resource_translations.<resource>.fields.<field_id>— resource-scoped, so you can override a label for a single resource.avo.field_translations.<field_id>— shared across every resource that uses this field id.- The humanized field id — the fallback when neither key is translated.
Setting translation_key: explicitly bypasses the cascade and uses only that key.
# app/avo/resources/project.rb
class Avo::Resources::Project < Avo::BaseResource
self.title = :name
def fields
field :id, as: :id
# ... other fields
field :files, as: :files, translation_key: 'avo.field_translations.file'
end
end# avo.es.yml
es:
avo:
dashboard: 'Dashboard'
# ... other translation keys
field_translations:
file:
zero: 'Archivos'
one: 'Archivo'
other: 'Archivos'To override a field label for a single resource, add a resource-scoped entry. It wins over the shared field_translations key, while other resources keep using the shared one:
# avo.en.yml
en:
avo:
resource_translations:
product:
fields:
title:
one: "Product title"
other: "Product titles"
field_translations:
title:
one: "Title" # shared fallback for every other resource
other: "Titles"When no explicit help:, placeholder:, or include_blank: is set on the field, Avo also resolves those from sibling keys under the same translation_key:
# avo.sv.yml
sv:
avo:
field_translations:
dates:
one: "Datumintervall"
other: "Datumintervall"
help: "Valfritt. Standardperiod: 1 vecka tillbaka till idag."
placeholder: "Välj datum"
include_blank: "Ingen"# No help/placeholder/include_blank needed in Ruby — they come from the locale file
field :dates, as: :date_timeYou can point translation_key at a resource-scoped path if you prefer to keep field copy next to the resource:
avo:
resource_translations:
import_guesty:
fields:
dates:
help: Optional. Standard period: 1 week ago to present.
placeholder: Choose dates
include_blank: Nonefield :dates,
as: :date_time,
translation_key: "avo.resource_translations.import_guesty.fields.dates"Explicit help:, placeholder:, and include_blank: options (strings or lambdas) still win over the locale file.
Localizing tabs and panels
Tab and panel titles localize through the resource's translation key too. Avo parameterizes the configured title: (downcased, non-alphanumerics turned into _) and looks it up under a tabs or panels scope:
avo.resource_translations.<resource>.tabs.<title>for atabavo.resource_translations.<resource>.panels.<title>for apanel
So tab title: "Activity" resolves to avo.resource_translations.user.tabs.activity, and panel title: "Contact information" resolves to avo.resource_translations.user.panels.contact_information:
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
def fields
tabs do
tab title: "Activity" do
field :last_seen_at, as: :date_time
end
end
panel title: "Contact information" do
field :email, as: :text
end
end
end# avo.es.yml
es:
avo:
resource_translations:
user:
tabs:
activity: "Actividad"
panels:
contact_information: "Información de contacto"The configured title: stays the fallback whenever the key has no translation. If the generated key doesn't suit you, pass an explicit translation_key: to the tab or panel and Avo uses it verbatim:
tab title: "Activity", translation_key: "avo.resource_translations.user.tabs.recent_activity" do
# ...
endLocalizing buttons label
The avo.save configuration applies to all save buttons. If you wish to customize the localization for a specific resource, such as Avo::Resources::Product, you can achieve this by:
---
en:
avo:
resource_translations:
product:
save: "Save the product!"Setting the locale
Setting the locale for Avo is pretty simple. Just use the config.locale = :en config attribute. Default is nil and will fall back to whatever you have configured in as config.i18n.default_locale in application.rb.
Avo.configure do |config|
config.locale = :en # default is nil
endThat will change the locale only for Avo requests. The rest of your app will still use your locale set in application.rb. If you wish to change the locale for Avo, you can use the set_locale=pt-BR param. That will set the default locale for Avo until you restart your server.
Suppose you wish to change the locale only for one request using the force_locale=pt-BR param. That will set the locale for that request and keep the force_locale param in all links while you navigate Avo. Remove that param when you want to go back to your configured default_locale.
Related:
- Check out our guide for multilingual records.
Right-to-left locales
Avo detects right-to-left languages from the active locale and flips its layout automatically — no configuration needed. The built-in RTL locales are ar, he, fa, ur, yi, ps, sd, ku, ckb, ug, and dv. Matching is done on the language segment, so a regional variant like ar-EG counts as RTL too.
Customize the locale
If there's anything in the locale files that you would like to change, run bin/rails generate avo:locales to generate the locale files.
These provide a guide for you for when you want to add more languages.
If you do translate Avo in a new language please consider contributing it to the main repo. Thank you
FAQ
If you try to localize your resources and fields and it doesn't seem to work, please be aware of the following.
The I18n.t method defaults to the name of that field/resource
Internally the localization works like so I18n.t(translation_key, count: 1, default: default) where the default is the computed field/resource name. So check the structure of your translation keys.
The default is the only value Avo humanizes. When your key resolves, the translation is used exactly as written.
# config/locales/avo.pt-BR.yml
pt-BR:
avo:
field_translations:
file:
zero: 'Arquivos'
one: 'Arquivo'
other: 'Arquivos'
resource_translations:
user:
zero: 'Usuários'
one: 'Usuário'
other: 'Usuários'Using a Route Scope for Localization
To implement a route scope for localization within Avo, refer to this guide. It provides step-by-step instructions on configuring your routes to include a locale scope, enabling seamless localization handling across your application.