Skip to content

Translatable fields: ->translatable() hook for javaabu/translatable v3 #15

Closed

Description

@n4ff4h

javaabu/translatable v3 defines how packages support translations optionally (contract: n4ff4h/translatable docs/advanced-usage/for-package-authors.md on next-gen, summary on n4ff4h/translatable#10). Stencil fields need a hook so forms defined once render per-locale inputs in both drivers when the model is translatable. Translatable stays optional for stencil: suggest + require-dev, feature-detected.

What stencil needs to add

Text::make('name')->label(__('Name'))->required()->translatable();
Textarea::make('description')->translatable(fn (?Model $model) => $model?->is_public);
  • translatable(bool|Closure $translatable = true) on the base field (closure-evaluated like other props).
  • At render/serialize time a field is translatable when translatable is true and class_exists(\Javaabu\Translatable\Translatable::class) && \Javaabu\Translatable\Translatable::supports($model ?? $model_class, $field_name). Otherwise it renders exactly as today (so ->translatable() is harmless without the package or on a non-translatable model).
  • A translatable field expands into one input per locale with Translatable::inputs($field_name, $model) (a Collection<string, LocaleInput>, the model's primary locale first). Each LocaleInput gives:
    • name: name_en, name_dv (always {field}_{locale}, the default locale included)
    • locale (Locale: code, native_name, direction, isThaana()), is_primary
    • label($label): "Name (ދިވެހި)"
    • toArray(): { field, name, locale, direction, thaana, primary, language }
  • Validation: Translatable::rules($model, $rules) turns ['name' => ['required', 'string']] into name_en (primary, rules as written) and name_dv (required → nullable). Stencil's automatic validation should run its generated rules through it for translatable fields.
  • Binding: values come from Translatable::values($model, ['name']) (['name_en' => 'Malé', 'name_dv' => 'މާލެ']), or $model->name_dv, which forms' model binding already reads. Old input uses the same names.
  • Saving: $model->fill($validated) / create() / update() accept the per-locale names; translations are written on save. No stencil code needed.

Rendering

  • Blade driver: render <x-translatable::field type="text" name="name" :label="…" /> (or text/textarea) from translatable; it draws per-locale tabs of <x-forms::text name="name_dv" dir="rtl" thaana …> and handles errors and old input. Its attributes are the forms v2 attributes plus layout="tabs|stacked".
  • React driver: serialize a translatable field as the plain field plus translatable: { inputs: [LocaleInput::toArray(), …] }. Render one @javaabu/forms field per input (name, dir, thaana, label from language.native_name), grouped in tabs. Don't import @javaabu/translatable from stencil's React code (it only exists in apps that install translatable); the serialized data is enough. Apps can still map it to translatable's TranslatableField, which takes the same inputs.

Asks

  1. ->translatable() on fields, evaluated as above.
  2. Both drivers expanding via Translatable::inputs() and validation via Translatable::rules().
  3. Tests with translatable installed (Javaabu\Translatable\Testing\InteractsWithLanguages::setUpLanguages(['en', 'dv'])) and with it off (withoutTranslatable()), using Thaana values.

Require it for tests as "javaabu/translatable": "^3.0@dev" with { "type": "vcs", "url": "https://github.com/n4ff4h/translatable" }. If any of this doesn't fit stencil's architecture, comment here and I'll adjust translatable's side.

Activity

  1. added
    next-genNext-gen Javaabu packages
    cross-packageRequested by or affecting another next-gen package
    on Nov 4, 2026
  2. n4ff4h commented on Nov 4, 2026

    @n4ff4h
    CollaboratorAuthor

    Thanks, this fits. Here's what stencil will provide. Translatable stays optional: suggest, require-dev via the n4ff4h/translatable VCS repo at ^3.0@dev, and guarded with class_exists(\Javaabu\Translatable\Translatable::class).

    API. ->translatable(bool|Closure $condition = true) on the base Field. Closures get stencil's usual injection ($record, $model, $get, $operation, …). A field counts as translatable when the condition is true, the class exists, and Translatable::supports($record ?? $model_class, $name) passes. Otherwise nothing changes.

    State and binding. A translatable field holds one state entry per input: name_en, name_dv. These come from Translatable::values() and old input with the same names. $get('name_dv') works, and $get('name') returns the primary locale's value.

    Validation. The field's generated rules (required, string, max:…, custom rules) go through Translatable::rules(). Each input's attribute name is LocaleInput::label($label). validate() returns the per-locale keys, so fillModel()/save() hand them straight to fill().

    Blade driver. Renders <x-translatable::field type="{forms component}" name="name" :label …> with the field's other forms props as attributes, plus layout. I'll add ->translatableLayout('tabs'|'stacked') as a pass-through.

    JSON/React. The node keeps its usual shape (type, name, component, props) and adds:

    "translatable": { "layout": "tabs", "inputs": [ { "field": "name", "name": "name_en", "locale": "en", "direction": "ltr", "thaana": false, "primary": true, "language": { … } }, … ] }

    The React renderer draws one @javaabu/forms component per input (name, dir, thaana, label from language.native_name), grouped in @javaabu/forms Tabs. It doesn't import @javaabu/translatable. Apps can register their own component for translatable fields through the stencil React registry, to use translatable's TranslatableField.

    Conditions. visibleWhen('name', …) on a translatable field watches the primary input.

    Tests. Covered both ways: with InteractsWithLanguages::setUpLanguages(['en', 'dv']) and with withoutTranslatable(), using Thaana values.

    This comes after the core drivers land. I'll comment here when it's pushed.

  3. n4ff4h commented on Nov 4, 2026

    @n4ff4h
    CollaboratorAuthor

    Done in a21ec03, as agreed above. Docs are in docs/advanced-usage/translatable.md.

    One deviation from the proposal: the Blade driver doesn't render <x-translatable::field>. It renders the same per-locale inputs itself, with javaabu/forms tabs and inputs (dir, lang, Thaana, "Name (ދިވެހި)" labels, the tab with an error opens). The reason is that <x-translatable::field> binds to the model, while stencil fields bind to the form's state, which has to carry the user's edits through a live re-render. The React renderer draws the same inputs from the node's translatable key, and apps can register translatable's own component as the translatable type.

    Translatable is optional:

    • suggest, plus require-dev from the n4ff4h VCS repository.
    • Every call is guarded by class_exists().
    • Tests use setUpLanguages(['en', 'dv']) and withoutTranslatable().
    • A CI leg runs the suite with the package removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cross-packageRequested by or affecting another next-gen packagenext-genNext-gen Javaabu packages

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions