$npx -y skills add ThibautBaissac/rails_ai_agents --skill i18n-patternsImplements internationalization with Rails I18n for multi-language support. Use when adding translations, managing locales, localizing dates/currencies, pluralization, or when user mentions i18n, translations, locales, or multi-language. WHEN NOT: English-only applications withou
| 1 | # I18n Patterns for Rails 8 |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Rails I18n provides internationalization support: |
| 6 | - Translation lookups |
| 7 | - Locale management |
| 8 | - Date/time/currency formatting |
| 9 | - Pluralization rules |
| 10 | - Lazy lookups in views |
| 11 | |
| 12 | ## Quick Start |
| 13 | |
| 14 | ```ruby |
| 15 | # config/application.rb |
| 16 | config.i18n.default_locale = :en |
| 17 | config.i18n.available_locales = [:en, :fr, :de] |
| 18 | config.i18n.fallbacks = true |
| 19 | ``` |
| 20 | |
| 21 | ## Project Structure |
| 22 | |
| 23 | ``` |
| 24 | config/locales/ |
| 25 | ├── en.yml # English defaults |
| 26 | ├── fr.yml # French defaults |
| 27 | ├── models/ |
| 28 | │ ├── en.yml # Model translations (EN) |
| 29 | │ └── fr.yml # Model translations (FR) |
| 30 | ├── views/ |
| 31 | │ ├── en.yml # View translations (EN) |
| 32 | │ └── fr.yml # View translations (FR) |
| 33 | ├── mailers/ |
| 34 | │ ├── en.yml # Mailer translations (EN) |
| 35 | │ └── fr.yml # Mailer translations (FR) |
| 36 | └── components/ |
| 37 | ├── en.yml # Component translations (EN) |
| 38 | └── fr.yml # Component translations (FR) |
| 39 | ``` |
| 40 | |
| 41 | ## Locale File Organization |
| 42 | |
| 43 | Organize locale files by domain: `models/`, `views/`, `mailers/`, `components/`. |
| 44 | |
| 45 | - **Models:** `activerecord.models`, `activerecord.attributes`, `activerecord.errors` |
| 46 | - **Views:** nested under controller name and action (e.g. `events.index.title`) |
| 47 | - **Shared:** `common.actions`, `common.messages`, `common.date.formats` |
| 48 | - **Components:** `components.<component_name>.<key>` |
| 49 | |
| 50 | See [locale-files.md](references/locale-files.md) for complete YAML examples for models, views, shared keys, and components. |
| 51 | |
| 52 | ## Usage Patterns |
| 53 | |
| 54 | ### Key Principles |
| 55 | |
| 56 | - Use **lazy lookup** in views: `t(".title")` resolves to `"events.index.title"` |
| 57 | - Use **`_html` suffix** for strings containing HTML markup |
| 58 | - Use **`I18n.l`** (localize) for dates, times, and numbers — not `I18n.t` |
| 59 | - Use **`I18n.t`** with full key path in models, services, and presenters |
| 60 | - Pass dynamic values via **interpolation**: `t(".greeting", name: user.name)` |
| 61 | |
| 62 | ### In Views |
| 63 | |
| 64 | ```erb |
| 65 | <h1><%= t(".title") %></h1> |
| 66 | <%= link_to t(".new_event"), new_event_path %> |
| 67 | <p><%= t(".welcome", name: current_user.name) %></p> |
| 68 | <p><%= t(".intro_html", link: link_to("here", help_path)) %></p> |
| 69 | ``` |
| 70 | |
| 71 | ### In Controllers |
| 72 | |
| 73 | ```ruby |
| 74 | redirect_to @event, notice: t(".success") |
| 75 | ``` |
| 76 | |
| 77 | ### In Models/Presenters |
| 78 | |
| 79 | ```ruby |
| 80 | I18n.t("activerecord.attributes.event/statuses.#{status}") |
| 81 | I18n.l(event_date, format: :long) |
| 82 | ``` |
| 83 | |
| 84 | See [usage-patterns.md](references/usage-patterns.md) for full examples including presenters, components, date/currency formatting, and pluralization. |
| 85 | |
| 86 | ## Locale Switching |
| 87 | |
| 88 | ### URL-Based Locale |
| 89 | |
| 90 | ```ruby |
| 91 | # config/routes.rb |
| 92 | Rails.application.routes.draw do |
| 93 | scope "(:locale)", locale: /en|fr|de/ do |
| 94 | resources :events |
| 95 | end |
| 96 | end |
| 97 | |
| 98 | # app/controllers/application_controller.rb |
| 99 | class ApplicationController < ActionController::Base |
| 100 | around_action :switch_locale |
| 101 | |
| 102 | private |
| 103 | |
| 104 | def switch_locale(&action) |
| 105 | locale = params[:locale] || I18n.default_locale |
| 106 | I18n.with_locale(locale, &action) |
| 107 | end |
| 108 | |
| 109 | def default_url_options |
| 110 | { locale: I18n.locale } |
| 111 | end |
| 112 | end |
| 113 | ``` |
| 114 | |
| 115 | ### User Preference Locale |
| 116 | |
| 117 | ```ruby |
| 118 | class ApplicationController < ActionController::Base |
| 119 | around_action :switch_locale |
| 120 | |
| 121 | private |
| 122 | |
| 123 | def switch_locale(&action) |
| 124 | locale = current_user&.locale || extract_locale_from_header || I18n.default_locale |
| 125 | I18n.with_locale(locale, &action) |
| 126 | end |
| 127 | |
| 128 | def extract_locale_from_header |
| 129 | request.env['HTTP_ACCEPT_LANGUAGE']&.scan(/^[a-z]{2}/)&.first |
| 130 | end |
| 131 | end |
| 132 | ``` |
| 133 | |
| 134 | ## Testing I18n |
| 135 | |
| 136 | - Raise on missing translations in `spec/rails_helper.rb` |
| 137 | - Use `i18n-tasks` gem to detect missing and unused keys |
| 138 | - Write view translation specs to assert rendered content |
| 139 | |
| 140 | See [testing.md](references/testing.md) for complete spec examples and i18n-tasks configuration. |
| 141 | |
| 142 | ## Best Practices |
| 143 | |
| 144 | ### DO |
| 145 | |
| 146 | ```yaml |
| 147 | # Use nested structure matching view paths |
| 148 | en: |
| 149 | events: |
| 150 | index: |
| 151 | title: Events |
| 152 | show: |
| 153 | title: Event Details |
| 154 | |
| 155 | # Use interpolation for dynamic content |
| 156 | en: |
| 157 | greeting: "Hello, %{name}!" |
| 158 | |
| 159 | # Use _html suffix for HTML content |
| 160 | en: |
| 161 | intro_html: "Welcome to <strong>our app</strong>" |
| 162 | ``` |
| 163 | |
| 164 | ### DON'T |
| 165 | |
| 166 | ```yaml |
| 167 | # Don't use flat keys |
| 168 | en: |
| 169 | events_index_title: Events # BAD |
| 170 | |
| 171 | # Don't hardcode in views |
| 172 | <h1>Events</h1> # BAD - use t(".title") |
| 173 | |
| 174 | # Don't concatenate translations |
| 175 | t("hello") + " " + t("world") # BAD |
| 176 | ``` |
| 177 | |
| 178 | ## Checklist |
| 179 | |
| 180 | - [ ] Locale files organized by domain (models, views, etc.) |
| 181 | - [ ] All user-facing text uses I18n |
| 182 | - [ ] Lazy lookups in views (t(".key")) |
| 183 | - [ ] Pluralization for countable items |
| 184 | - [ |