Shadwire

Search the documentation

Search the documentation and component pages by title and by content.

Get started
Introduction
Installation
shadwire.json
Theming
Dark mode
Tools
CLI
Agent skill
Registry
llms.txt
Guides
Composition
Styling
Forms
Icons
Accessibility
Localisation
Blocks
Overview
Components
Overview
Accordion
Alert
Alert Dialog
Aspect Ratio
Avatar
Badge
Breadcrumb
Button
Button Group
Calendar
Card
Carousel
Chart
Checkbox
Collapsible
Combobox
Command
Context Menu
Data Table
Date Picker
Dialog
Drawer
Dropdown Menu
Empty
Field
Hover Card
Icon
Input
Input Group
Input OTP
Item
Kbd
Label
Menubar
Native Select
Navigation Menu
Pagination
Popover
Progress
Radio Group
Resizable
Scroll Area
Select
Separator
Sheet
Sidebar
Skeleton
Slider
Sonner
Spinner
Switch
Table
Tabs
Textarea
Toggle
Toggle Group
Tooltip

Localisation

Installed components look up their text in I18n and fall back to English. If your app defines a key, the component uses your text instead.

Any text a component renders by itself, such as a close button's label, a pagination arrow or an empty table's message, goes through I18n.t with the English text as the default:

# app/components/ui/dialog/content_component.rb, as installed
I18n.t("ui.dialog.close", default: "Close")

Because of that default, a component works right after you install it: with no locale file, it renders in English. You don't have to configure anything first, and apps that don't use I18n at all are not affected.

Overriding a string

Define the key in your own locale file. You don't need to register anything or pass an argument to the component; the key is all there is to it.

# config/locales/pt.yml
pt:
  ui:
    dialog:
      close: Fechar
    sheet:
      close: Fechar
    pagination:

Only the keys you define change. The rest keep their English defaults, so you can translate one component at a time.

The keys

Every key a component reads, with its default text. Keys marked value are written into a data-*-value attribute and used by a Stimulus controller instead of being printed by the server; the next section explains why that matters.

breadcrumb

Key Default Kind
ui.breadcrumb.label breadcrumb server
ui.breadcrumb.more More server

calendar

Key Default Kind
ui.calendar.month Month value
ui.calendar.next_month Next month server
ui.calendar.previous_month Previous month server
ui.calendar.year Year value

carousel

Key Default Kind
ui.carousel.next Next slide server
ui.carousel.previous Previous slide server
ui.carousel.roledescription carousel server
ui.carousel.slide slide server

data_table

Key Default Kind
ui.data_table.columns Columns server
ui.data_table.empty No results. server
ui.data_table.filter_placeholder Filter… server
ui.data_table.next Next server
ui.data_table.page Page %{page} of %{total} value
ui.data_table.previous Previous server
ui.data_table.select_all Select all rows server
ui.data_table.select_row Select row server
ui.data_table.selection %{selected} of %{total} row(s) selected. value

dialog

Key Default Kind
ui.dialog.close Close server

pagination

Key Default Kind
ui.pagination.label pagination server
ui.pagination.more More pages server
ui.pagination.next Next server
ui.pagination.next_label Go to the next page server
ui.pagination.previous Previous server
ui.pagination.previous_label Go to the previous page server

sheet

Key Default Kind
ui.sheet.close Close server

sidebar

Key Default Kind
ui.sidebar.toggle Toggle Sidebar server

sonner

Key Default Kind
ui.sonner.close Close value
ui.sonner.label Notifications server

spinner

Key Default Kind
ui.spinner.label Loading server

Five of them are read by JavaScript

ui.data_table.selection, ui.data_table.page, ui.calendar.month, ui.calendar.year and ui.sonner.close are not printed on the page. They are looked up once, when the component renders, and written into a data-*-value attribute for the controller to read. You translate them the same way as the others, since they are ordinary I18n keys.

Two of them need care. The data table's selection count and page counter are filled in by the controller, not by I18n, so your translation has to keep their placeholders:

pt:
  ui:
    data_table:
      # The Stimulus controller substitutes these tokens, not I18n.
      # Keep every one of them, in any order.
      selection: "%{selected} de %{total} linha(s) selecionada(s)."
      page: "Página %{page} de %{total}"

If you translate or remove %{selected}, %{page} or %{total}, the caption shows the placeholder as text or no number at all, and you won't get an error, because I18n never sees these placeholders.

Month and day names come from Rails

The calendar's caption and column headers are not in the ui. namespace. The calendar reads them from Rails' own date translations, date.month_names and date.abbr_day_names. They are in English by default, and in your language once your app loads the translations, either from rails-i18n or from your own locale file. To use different names in one calendar, pass them in:

<%# Abbreviated months in the caption, two-letter column headers %>
<%= ui_calendar(month_names: I18n.t("date.abbr_month_names").compact,
                day_names: I18n.t("date.abbr_day_names").map { |name| name.first(2) }) %>

You need the .compact: Rails' date.abbr_month_names starts at index 1, so its first element is nil.

A worked example

This site is one. Its Portuguese pages render the same components you install, and the site defines the whole ui. block once. That is why the text the components add themselves on /pt (close buttons, pagination arrows, the empty table message) is in Portuguese.

sandbox/config/locales/pt.yml has every key on this page, and you can copy it as a starting point for an app in Portuguese.