Skip to main content
Copy for LLM

Translatable Input

The <x-wirekit::translatable-input> component edits one value in several languages: a product name, a category, a shipping method's description. The primary language's field comes first. Under it, every further language has a chip that says whether it is translated, and a field of its own. With a few languages every field is on screen; with many, a chip opens its language, one at a time.

Usage

Three languages
Product name

The first language of locales is the primary one unless primary names another. A translation that is still missing shows the primary text as its placeholder, so the reader sees what to translate, and its chip says "missing" until something is typed.

Many Languages

With more further languages than inline (2 by default), the fields of the further languages close. A chip opens its language and closes the one open before, and "Show all languages" opens every one of them, in two columns from the sm breakpoint on.

Nine languages, one at a time
Category

A chip shows its language's code, a check for translated, a dash for missing and a warning sign for a field the server rejected. The state is in the chip's accessible name as well, "Italiano: missing", so it never rests on its color.

With Livewire

wire:model on the component binds each language to its key of one array, name.de, name.en, with the modifiers you give it:

// app/Livewire/EditCategory.php
use Livewire\Component;

class EditCategory extends Component
{
    // 1. One array per value, keyed by the language code
    public array $name = ['de' => '', 'en' => '', 'fr' => ''];

    public function save(): void
    {
        // 2. The primary language is required, a translation is not
        $this->validate([
            'name.de' => ['required', 'string', 'max:120'],
            'name.*' => ['nullable', 'string', 'max:120'],
        ]);
    }
}
{{-- resources/views/livewire/edit-category.blade.php --}}
<x-wirekit::translatable-input
    label="Category"
    :locales="['de' => 'Deutsch', 'en' => 'English', 'fr' => 'Français']"
    wire:model.blur="name"
    required
/>

An error the server files under one language, name.fr, stands under that language's field, its chip shows the warning sign, and a closed field opens. An error filed under the value itself, name, stands under the legend, and every language's field says it is invalid and points at it. required marks the primary language only: a translation stays optional.

Error state
Product name

Give the name in at least one language.

Like every field, it takes a labelAfter slot for a mark after the legend, a badge that says the value is not decided yet, for example, as described for Input.

In a Plain Form

Without wire:model, name is the base of the fields' names, name[de], name[en], and value fills them:

<form method="POST" action="/categories/12">
    @csrf
    @method('PUT')

    <x-wirekit::translatable-input
        label="Category"
        name="name"
        :locales="['de' => 'Deutsch', 'en' => 'English']"
        :value="old('name', $category->name)"
    />
</form>

$category->name is the array of texts, as a JSON column cast to array stores it. Laravel reads the request back as the same array, and an error under name.en reaches the English field. old('name', …) brings back what the reader typed when the request fails validation; with $category->name alone, every language would show the stored text again and the edits would be gone.

Several Lines

multiline gives every language a Textarea, with rows lines:

A description in three languages
Description

Shown on the product page.

Naming the Languages

Give locales as a map of code to name, in the words your readers know. A list of codes works too: each language is then named in the reader's language through PHP's intl extension, en as "Englisch" for a German reader, and by its code where intl is not installed. A code is letters, digits, - and _ (pt-BR, zh_Hant); anything else is left out, with a warning in the log while APP_DEBUG is on.

Props

Prop Type Default Description
label string|null null The value's name, the legend of the group
locales array [] The languages, a map of code to name or a list of codes
primary string|null null The primary language's code. Without it, the first of locales
value array|null null The text per language, for a form without wire:model
name string|null null The base of the fields' names, name[de]
multiline bool false A textarea per language instead of an input
rows int|string 3 The lines of each textarea, or auto to grow with its text
inline int 2 How many further languages stand as fields without asking
hint string|null null A sentence under the legend
help string|null null An explanation in a tooltip beside the legend
error string|null null A message about the value as a whole. Without it, the error bag's message for name or the model
required bool false Marks and requires the primary language
placeholder string|null null The primary field's placeholder
disabled bool false Disables every language
readonly bool false Makes every language read-only
size string 'md' sm, md or lg, as on Input
scope string|null null Scoped personalization key

Accessibility

  • The value is a fieldset named by its legend, and each field is named by its language in full, so a screen reader announces "Product name, group" and then "English, edit text".
  • Each field carries lang with its code, so a screen reader reads the text in that language's pronunciation (WCAG 3.1.2).
  • A chip is a button. Its name is the language and its state, "English: translated"; the code and the glyph on it are hidden from screen readers, and the colors follow the state without carrying it alone (WCAG 1.4.1).
  • Where a chip opens its language, it carries aria-expanded and aria-controls, and the focus moves to the field it opened. "Show all languages" carries aria-expanded as well.
  • A field the server rejected opens and stays open, so its message is in reach.

Keyboard Interaction

Key Action
Tab Moves through the primary field, the chips, "Show all languages" and the open fields
Enter / Space On a chip: opens its language and moves to its field, or closes it; with every field on screen, moves to its field

Design Tokens

Token Used for
--color-wk-bg-muted The code beside each field
--color-wk-text-muted A further language's code, and a chip before it is translated
--color-wk-text The primary language's code
--color-wk-border-strong A chip's edge
--color-wk-bg-hover A chip under the pointer
--color-wk-border-success The edge of a translated chip
--color-wk-success-text The text of a translated chip
--color-wk-border-error The edge of a chip whose field has an error
--color-wk-danger-text The text of a chip whose field has an error
--color-wk-bg-active The chip of the open language
--gap-wk-sm The space between a code and its field, and between two fields
--gap-wk-xs The space between the chips

The fields are Input and Textarea, and the group is a field set, each with its own tokens.

Further Reading

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store