Skip to content

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.

ruby
# app/avo/resources/user.rb
class Avo::Resources::User < Avo::BaseResource
  self.title = :name
  self.translation_key = 'avo.resource_translations.user'
end
yaml
# 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).

ruby
# 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
yaml
# 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:

  1. avo.resource_translations.<resource>.fields.<field_id> — resource-scoped, so you can override a label for a single resource.
  2. avo.field_translations.<field_id> — shared across every resource that uses this field id.
  3. The humanized field id — the fallback when neither key is translated.

Setting translation_key: explicitly bypasses the cascade and uses only that key.

ruby
# 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
yaml
# 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:

yaml
# 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:

yaml
# 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"
ruby
# No help/placeholder/include_blank needed in Ruby — they come from the locale file
field :dates, as: :date_time

You can point translation_key at a resource-scoped path if you prefer to keep field copy next to the resource:

yaml
avo:
  resource_translations:
    import_guesty:
      fields:
        dates:
          help: Optional. Standard period: 1 week ago to present.
          placeholder: Choose dates
          include_blank: None
ruby
field :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 a tab
  • avo.resource_translations.<resource>.panels.<title> for a panel

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:

ruby
# 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
yaml
# 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:

ruby
tab title: "Activity", translation_key: "avo.resource_translations.user.tabs.recent_activity" do
  # ...
end

Localizing 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:

yml
---
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.

ruby
Avo.configure do |config|
  config.locale = :en # default is nil
end

That 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:

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.

yaml
# 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.