Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
alfakit-localization
alfasix/alfakit-workflows
claude-code
workflow
Audit
laravel
state-machine
approvals
alfakit

Make AlfaKit panels multilingual and global — panel locales and the language menu, user language preference, RTL, translating labels and model names, adding languages, plural strings, currencies and money fields, numbers, dates and timezones. Use for any translation, RTL, currency or timezone work.

Install
php artisan boost:add-skill alfasix/alfakit-workflows

Save this content to: .claude/skills/alfakit-localization/SKILL.md

# AlfaKit localization

## When to use this skill

Use it when adding languages, fixing a translation, supporting Arabic, Hebrew or other RTL languages, showing or entering money, or handling user timezones.

## Languages

```php
$panel
    ->locales(['en', 'ar', 'fr', 'pt_BR'])   // language menu appears automatically
    ->defaultLocale('en')
    ->languageSwitcher('topbar')            // 'user-menu' (default), 'topbar', 'both', 'hidden'
    ->persistLocaleUsing(fn (User $user, string $locale) => $user->update(['locale' => $locale]));
```

- `languageSwitcher()` (also `AlfaSix\Alfakit\Support\Enums\LanguageSwitcherPosition::Topbar`) puts the language menu in the user menu, a top bar languages button next to the theme switcher, both, or nowhere in the panel. Auth pages always keep their button; `localeSwitcher(false)` turns every menu off. Languages are listed by their own name (`Locale::make('ja')->getLabel()` is `日本語`). The frontend reads `localization.switcherPosition`; renderers place the button with `showsLanguageSwitcher(localization, 'topbar' | 'user-menu' | 'auth')` from `@alfakit/core`, as `data-slot="language-switcher"`, replaceable through the `panel:language-switcher` registry key. On phones (below `sm`) the top bar button of `'topbar'` / `'both'` moves into the user menu (`collapsesLanguageSwitcher(localization, hasUserMenu)`; the button is wrapped in `data-slot="topbar-language"`), unless there is no user menu (guests, `userMenu(false)`).
- Starter kit panels (`->starterKit()`) render no top bar or user menu, so the menu is the drop-in `<AlfakitLanguageSwitcher />` (`@alfakit/react`, `@alfakit/vue`, `@alfakit/svelte`; optional `panel` prop) in the app header: `alfakit:install --starter-kit` adds it to the `alfakit:header` block, and running it again (`--no-npm`, same `--panel`) adds it to a block written earlier. It renders nothing with fewer than two `locales()` or with `localeSwitcher(false)`, ignores `languageSwitcher()` positions, shows the current language at once from `alfakitNavigation[].widgets.language` (`{ label, current: { code, tag, label, direction } }`) and loads its menu (`LocaleOptions`) with the panel data; a pick posts to `_alfakit/locale` and reloads, so AlfaKit pages and header components switch, and `<html dir>` follows the new language on every page (see RTL).

- Built-in strings in 12 languages: `composer require alfasix/alfakit-translations`.
- Validation messages are Laravel's (`lang/{locale}/validation.php`), not AlfaKit's: add them for each language (e.g. `composer require laravel-lang/common --dev` and `php artisan lang:add ar`).
- Read the user's language by implementing `Illuminate\Contracts\Translation\HasLocalePreference` on the user model.
- The order is: switcher choice in this session, then the user preference, cookie, `Accept-Language`, and finally the default.
- Any other language: `php artisan alfakit:lang:add sw`, translate `lang/vendor/alfakit/sw/*.php`, then add `'sw'` to `locales()`.
- Check completeness: `php artisan alfakit:lang:check -v`. It fails on missing strings or broken `:placeholders`, so it fits CI.
- Override single strings in `lang/vendor/alfakit/{locale}/{group}.php`.
- A regional or script variant (`ar_EG`, `pt_PT`) with no pack of its own takes its base language's strings (`ar`), then English; list `ar_EG` (or `ar_SA`) in `locales()` for Arabic-Indic digits with the Arabic strings.

## Your labels and model names

- Wrap labels in `__()`. Or call `->translateLabel()` (globally: `Field::configureUsing(fn (Field $f) => $f->translateLabel())`) to look up generated labels in `lang/{locale}.json`.
- Model names: `lang/{locale}/models.php` with `'category' => ['singular' => 'تصنيف', 'plural' => 'تصنيفات']` or `'category' => 'Kategorie|Kategorien'`. Navigation, titles, buttons and breadcrumbs follow. URLs stay English (`/categories`).
- Frontend strings for custom components: `$panel->frontendTranslations(['billing' => 'billing'])`, then `t('billing.key')` and `choice('billing.items', $count)` (Laravel `trans_choice` syntax with CLDR plural rules).

## Translatable content

`TextInput::make('title')->required()->translatable()` (also `Textarea`, `RichEditor`, `MarkdownEditor`) edits one value per panel locale (or `->translatable(locales: ['en', 'ar'])`) with a language switcher; the state is `['en' => '…', 'ar' => '…']`. Storage: array/JSON casts get the array, an uncast text column a JSON string, and spatie/laravel-translatable `HasTranslations` attributes are loaded with `getTranslations()` and saved as an array (spatie is optional). `required()` applies to the default locale or `->requiredLocales(['en', 'ar'])`; other rules apply to every language and errors are keyed `title.ar`. `LocaleSwitcher::make()` (`AlfaSix\Alfakit\Actions\LocaleSwitcher`) in a page's `getHeaderActions()` lets the user pick the language every translatable field opens on; it is kept in the session per panel (`TranslatableLocale::get()` / `::set()`).

## RTL

RTL is automatic for RTL languages, including any script subtag such as `pa_Arab`. The layout, sidebar, icons, charts and inputs all mirror. In starter-kit mode (`->starterKit()`) the whole page mirrors while the panel language is right to left, the host's own pages and the kit's sidebar and header included: AlfaKit shares `alfakitDocument` (`{ tag, direction }`: the page's own language, the panel language's direction) with every Inertia page and the renderers put it on `<html lang dir>` after each visit; `alfakit:install --starter-kit` adds `dir="{{ \AlfaSix\Alfakit\Support\StarterKitDocument::direction() }}"` to `resources/views/app.blade.php`, sets the kit's `<Sidebar side>` from `useAlfakitDirection()` (`'ltr'|'rtl'`; Vue a `ComputedRef`, Svelte `.current`) so it docks on the right, sets the header layout's `<SheetContent side>` the same way, and makes the kit's logo/user info/menus, header layout, password field reveal button, appearance tabs, two-factor copy button, split auth layout and (React) login link logical (`ms-`, `me-`, `pe-`, `end-`, `rounded-e-`, `border-s`, `text-start`; only known class lists, a changed file is skipped with a note); `standalone.css` turns breadcrumb chevrons around. Existing apps: re-run `php artisan alfakit:install --starter-kit --no-npm` (same `--panel`/`--path`); `alfakit:upgrade` reminds them. Display values in `<bdi>` use `isolationDirection(value)` from `@alfakit/core` (`ltr` without right-to-left letters, so Arabic-locale money like `‏0.00 US$` is not reordered; `auto` otherwise). Navigation and dashboard widgets shared with host pages are built in the panel's language. Custom components must use logical Tailwind classes (`ms`, `ps`, `start`, `end`, `text-start`, `border-s`) and `rtl:rotate-180` on arrows. Bundle a font that covers the script, such as Noto Sans Arabic or Vazirmatn.

## Money

```php
$panel->currency('EUR');                                          // default for the panel
TextColumn::make('total')->money(fn (Order $r) => $r->currency, inMinorUnits: true);
TextEntry::make('price')->money('BHD');
Select::make('currency')->currencies()->searchable()->live();
MoneyInput::make('total')->currency(fn (Get $get) => $get('currency'))->minorUnits()->minValue(0);
```

- Store money as integer minor units. The divisor follows the currency (USD 100, BHD 1000, JPY 1), and `Currency::decimals('BHD')` returns 3.
- `MoneyInput` accepts the locale's separators and digits and validates `decimal:0,{digits}`.
- PHP helpers: `Currency::format()`, `toMinorUnits()`, `fromMinorUnits()`, `name()`, `symbol()` and `options()`.

## Dates and timezones

- `$panel->timezone(fn () => auth()->user()?->timezone)`. Storage stays in `app.timezone`, while display and `DateTimePicker` input use the user's timezone. Date-only fields are never shifted.
- Columns: `->date()`, `->dateTime()` and `->since()` follow the user's language. Pickers start the week on the locale's first day.
- Option lists: `->timezones()`, `->languages()` and `->panelLocales()` on selects.

## Pitfalls

- Never concatenate translated fragments. Use one key with `:placeholders`.
- Never format money with `number_format()` or JavaScript `toFixed()` in views. Use `money()`, `Currency::format()` or `useFormatter()`.
- Don't derive URLs or relationship names from translated labels.

AlfaKit localization

When to use this skill

Use it when adding languages, fixing a translation, supporting Arabic, Hebrew or other RTL languages, showing or entering money, or handling user timezones.

Languages

$panel
    ->locales(['en', 'ar', 'fr', 'pt_BR'])   // language menu appears automatically
    ->defaultLocale('en')
    ->languageSwitcher('topbar')            // 'user-menu' (default), 'topbar', 'both', 'hidden'
    ->persistLocaleUsing(fn (User $user, string $locale) => $user->update(['locale' => $locale]));
  • languageSwitcher() (also AlfaSix\Alfakit\Support\Enums\LanguageSwitcherPosition::Topbar) puts the language menu in the user menu, a top bar languages button next to the theme switcher, both, or nowhere in the panel. Auth pages always keep their button; localeSwitcher(false) turns every menu off. Languages are listed by their own name (Locale::make('ja')->getLabel() is 日本語). The frontend reads localization.switcherPosition; renderers place the button with showsLanguageSwitcher(localization, 'topbar' | 'user-menu' | 'auth') from @alfakit/core, as data-slot="language-switcher", replaceable through the panel:language-switcher registry key. On phones (below sm) the top bar button of 'topbar' / 'both' moves into the user menu (collapsesLanguageSwitcher(localization, hasUserMenu); the button is wrapped in data-slot="topbar-language"), unless there is no user menu (guests, userMenu(false)).

  • Starter kit panels (->starterKit()) render no top bar or user menu, so the menu is the drop-in <AlfakitLanguageSwitcher /> (@alfakit/react, @alfakit/vue, @alfakit/svelte; optional panel prop) in the app header: alfakit:install --starter-kit adds it to the alfakit:header block, and running it again (--no-npm, same --panel) adds it to a block written earlier. It renders nothing with fewer than two locales() or with localeSwitcher(false), ignores languageSwitcher() positions, shows the current language at once from alfakitNavigation[].widgets.language ({ label, current: { code, tag, label, direction } }) and loads its menu (LocaleOptions) with the panel data; a pick posts to _alfakit/locale and reloads, so AlfaKit pages and header components switch, and <html dir> follows the new language on every page (see RTL).

  • Built-in strings in 12 languages: composer require alfasix/alfakit-translations.

  • Validation messages are Laravel's (lang/{locale}/validation.php), not AlfaKit's: add them for each language (e.g. composer require laravel-lang/common --dev and php artisan lang:add ar).

  • Read the user's language by implementing Illuminate\Contracts\Translation\HasLocalePreference on the user model.

  • The order is: switcher choice in this session, then the user preference, cookie, Accept-Language, and finally the default.

  • Any other language: php artisan alfakit:lang:add sw, translate lang/vendor/alfakit/sw/*.php, then add 'sw' to locales().

  • Check completeness: php artisan alfakit:lang:check -v. It fails on missing strings or broken :placeholders, so it fits CI.

  • Override single strings in lang/vendor/alfakit/{locale}/{group}.php.

  • A regional or script variant (ar_EG, pt_PT) with no pack of its own takes its base language's strings (ar), then English; list ar_EG (or ar_SA) in locales() for Arabic-Indic digits with the Arabic strings.

Your labels and model names

  • Wrap labels in __(). Or call ->translateLabel() (globally: Field::configureUsing(fn (Field $f) => $f->translateLabel())) to look up generated labels in lang/{locale}.json.
  • Model names: lang/{locale}/models.php with 'category' => ['singular' => 'تصنيف', 'plural' => 'تصنيفات'] or 'category' => 'Kategorie|Kategorien'. Navigation, titles, buttons and breadcrumbs follow. URLs stay English (/categories).
  • Frontend strings for custom components: $panel->frontendTranslations(['billing' => 'billing']), then t('billing.key') and choice('billing.items', $count) (Laravel trans_choice syntax with CLDR plural rules).

Translatable content

TextInput::make('title')->required()->translatable() (also Textarea, RichEditor, MarkdownEditor) edits one value per panel locale (or ->translatable(locales: ['en', 'ar'])) with a language switcher; the state is ['en' => '…', 'ar' => '…']. Storage: array/JSON casts get the array, an uncast text column a JSON string, and spatie/laravel-translatable HasTranslations attributes are loaded with getTranslations() and saved as an array (spatie is optional). required() applies to the default locale or ->requiredLocales(['en', 'ar']); other rules apply to every language and errors are keyed title.ar. LocaleSwitcher::make() (AlfaSix\Alfakit\Actions\LocaleSwitcher) in a page's getHeaderActions() lets the user pick the language every translatable field opens on; it is kept in the session per panel (TranslatableLocale::get() / ::set()).

RTL

RTL is automatic for RTL languages, including any script subtag such as pa_Arab. The layout, sidebar, icons, charts and inputs all mirror. In starter-kit mode (->starterKit()) the whole page mirrors while the panel language is right to left, the host's own pages and the kit's sidebar and header included: AlfaKit shares alfakitDocument ({ tag, direction }: the page's own language, the panel language's direction) with every Inertia page and the renderers put it on <html lang dir> after each visit; alfakit:install --starter-kit adds dir="{{ \AlfaSix\Alfakit\Support\StarterKitDocument::direction() }}" to resources/views/app.blade.php, sets the kit's <Sidebar side> from useAlfakitDirection() ('ltr'|'rtl'; Vue a ComputedRef, Svelte .current) so it docks on the right, sets the header layout's <SheetContent side> the same way, and makes the kit's logo/user info/menus, header layout, password field reveal button, appearance tabs, two-factor copy button, split auth layout and (React) login link logical (ms-, me-, pe-, end-, rounded-e-, border-s, text-start; only known class lists, a changed file is skipped with a note); standalone.css turns breadcrumb chevrons around. Existing apps: re-run php artisan alfakit:install --starter-kit --no-npm (same --panel/--path); alfakit:upgrade reminds them. Display values in <bdi> use isolationDirection(value) from @alfakit/core (ltr without right-to-left letters, so Arabic-locale money like ‏0.00 US$ is not reordered; auto otherwise). Navigation and dashboard widgets shared with host pages are built in the panel's language. Custom components must use logical Tailwind classes (ms, ps, start, end, text-start, border-s) and rtl:rotate-180 on arrows. Bundle a font that covers the script, such as Noto Sans Arabic or Vazirmatn.

Money

$panel->currency('EUR');                                          // default for the panel
TextColumn::make('total')->money(fn (Order $r) => $r->currency, inMinorUnits: true);
TextEntry::make('price')->money('BHD');
Select::make('currency')->currencies()->searchable()->live();
MoneyInput::make('total')->currency(fn (Get $get) => $get('currency'))->minorUnits()->minValue(0);
  • Store money as integer minor units. The divisor follows the currency (USD 100, BHD 1000, JPY 1), and Currency::decimals('BHD') returns 3.
  • MoneyInput accepts the locale's separators and digits and validates decimal:0,{digits}.
  • PHP helpers: Currency::format(), toMinorUnits(), fromMinorUnits(), name(), symbol() and options().

Dates and timezones

  • $panel->timezone(fn () => auth()->user()?->timezone). Storage stays in app.timezone, while display and DateTimePicker input use the user's timezone. Date-only fields are never shifted.
  • Columns: ->date(), ->dateTime() and ->since() follow the user's language. Pickers start the week on the locale's first day.
  • Option lists: ->timezones(), ->languages() and ->panelLocales() on selects.

Pitfalls

  • Never concatenate translated fragments. Use one key with :placeholders.
  • Never format money with number_format() or JavaScript toFixed() in views. Use money(), Currency::format() or useFormatter().
  • Don't derive URLs or relationship names from translated labels.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource