gaiatools/laravel-type-bridge

A Laravel package for generating TypeScript/JavaScript enums and frontend translation files from your Laravel app.

Installs: 799

Dependents: 0

Suggesters: 0

Security: 0

Stars: 2

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/gaiatools/laravel-type-bridge

v1.1.2 2025-12-21 07:04 UTC

This package is auto-updated.

Last update: 2025-12-23 01:00:07 UTC


README

Quality gate

Gaia Tools Laravel Type Bridge

A Laravel package for generating TypeScript/JavaScript enums and frontend translation files from your Laravel app.

Features

  • Automatically discover and generate frontend enums from PHP backed enums
  • Opt-in enum generation using the #[GenerateEnum] attribute
  • Flexible discovery via configurable paths (supports Modules or custom structures)
  • Generate TypeScript or JavaScript output
  • Support for translation file generation (TS, JS, or JSON)
  • Configurable backed-enum discovery toggle and excludes

Requirements

  • PHP 8.2 or higher
  • Laravel 11.x orhigher

Installation

Install the package via Composer:

composer require gaiatools/laravel-type-bridge

Publish the configuration file:

Smart config publishing

php artisan type-bridge:publish

Or Laravel config publishing

php artisan vendor:publish --tag=type-bridge-config

Configuration

The configuration file config/type-bridge.php provides the following options (env variables in parentheses):

return [
    // Output format for all generated files: 'ts' or 'js'
    'output_format' => env('TYPE_BRIDGE_OUTPUT_FORMAT', 'ts'),

    // Max line length for ESLint disable directive
    // Set to 0 or negative to disable
    'max_line_length' => env('TYPE_BRIDGE_MAX_LINE_LENGTH', 120),

    // Whether to include trailing commas in generated objects and arrays
    'trailing_commas' => env('TYPE_BRIDGE_TRAILING_COMMAS', true),

    // Enum generation configuration
    'enums' => [
        // Output path (relative to resources directory)
        'output_path' => 'js/enums/generated',

        // Discovery configuration
        'discovery' => [
            'paths' => [
                app_path('Enums'),
            ],
            // When true: generates all backed enums
            // When false: generates ONLY enums with GenerateEnum attribute
            'generate_backed_enums' => true,
            // Exclude specific enums (by short name or FQCN)
            'excludes' => [],
        ],
    ],

    // Translation generation configuration
    'translations' => [
        // Output path (relative to resources directory)
        'output_path' => 'js/locales/generated',

        // Where to discover Laravel translation locales. You can provide:
        // - a string path, or an array of paths
        // - glob patterns are supported (e.g. base_path('Modules/*/Resources/lang'))
        // Precedence: later paths override earlier ones when the same keys exist.
        // If not configured, the fallback is Laravel's default lang directory (base_path('lang')).
        // Examples:
        // 'lang_paths' => [
        //     base_path('lang'),
        //     base_path('Modules/*/Resources/lang'),
        // ],
        'lang_paths' => null,

        // Target i18n library for syntax transformation
        // Options: 'i18next' (default, works for react-i18next too), 'vue-i18n', 'laravel'
        'i18n_library' => env('TYPE_BRIDGE_I18N_LIBRARY', 'i18next'),

        // Custom adapter class (optional - for users who want to provide their own)
        'custom_adapter' => null, // e.g., \App\TypeBridge\CustomAdapter::class
    ],
];

Notes:

  • Set TYPE_BRIDGE_MAX_LINE_LENGTH=0 to disable /* eslint-disable max-len */ insertion entirely.
  • Defaults are merged even if you don’t publish the config.
  • Discovery looks for enums under the configured paths only. Use generateBackedEnums=false to restrict output to enums marked with #[GenerateEnum].

Supported translation engines

The translator utilities generated by this package can work with multiple i18n libraries through a tiny "engine" interface (an object exposing t(key: string): string). Out of the box we support:

  • i18next
  • react-i18next (uses the same i18next engine)
  • vue-i18n

Tip: In config/type-bridge.php you can set translations.i18n_library to control how translation keys are generated/organized for your target library. The default i18next also covers react-i18next.

Usage

Basic Enum Generation

Create a backed enum in your Laravel application:

<?php

namespace App\Enums;

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Pending = 'pending';
}

Generate the frontend enum (TypeScript by default):

php artisan type-bridge:enums

This will create a TypeScript file at resources/js/enums/generated/Status.ts:

// !!!!
// This is a generated file.
// Do not manually change this file
// !!!!

export const Status = {
    Active: 'active',
    Inactive: 'inactive',
    Pending: 'pending',
} as const;

export type Status = typeof Status[keyof typeof Status];

To generate JavaScript instead of TypeScript:

php artisan type-bridge:enums --format=js

Check mode (CI drift detection)

Validate that your previously generated frontend enum files are still in sync with the current PHP enums without writing any files. This is ideal for CI to detect drift.

Run:

php artisan type-bridge:enums --check [--format=ts|js]

Behavior:

  • Discovers current PHP enums and computes the expected frontend entries.
  • Loads the previously generated frontend files from your configured output path.
  • Compares both keys and values.
    • New case → reported as added: + KEY: VALUE
    • Removed case → reported as removed: - KEY: VALUE
    • Changed value → reported as both add and remove: + KEY: NEW_VALUE and - KEY: OLD_VALUE
  • Exit codes:
    • 0 when everything is in sync
    • 1 when any difference is detected (suitable for failing CI)

Notes:

  • --format controls which frontend files to compare against by extension (ts or js). If omitted, the command uses your configured type-bridge.output_format.
  • Output lines are plain text by default for stable logs. In decorated terminals (e.g., running with --ansi), added lines render in green and removed lines in red. The enum header line intentionally remains unstyled.

Examples

In sync:

Checking enums against previously generated frontend files...
✅ Enums are in sync with generated frontend files.

Differences found:

❌ Enums differ from generated frontend files:

OrderStatus (resources/js/enums/generated/OrderStatus.ts)
  + SHIPPED: 'shipped'
  - CANCELLED: 'cancelled'

Run `php artisan type-bridge:enums --format=ts` to regenerate.

Value change:

❌ Enums differ from generated frontend files:

Status (resources/js/enums/generated/Status.ts)
  + PENDING: 'awaiting'
  - PENDING: 'pending'

Run `php artisan type-bridge:enums --format=ts` to regenerate.

Output (resources/js/enums/generated/Status.js):

// !!!!
// This is a generated file.
// Do not manually change this file
// !!!!

export const Status = {
    Active: 'active',
    Inactive: 'inactive',
    Pending: 'pending',
};

Opt-in Enum Generation

Use the #[GenerateEnum] attribute to explicitly mark enums for generation:

<?php

namespace App\Enums;

use GaiaTools\TypeBridge\Attributes\GenerateEnum;

#[GenerateEnum]
enum ThemeVisibility: string
{
    case Private = 'private';
    case Unlisted = 'unlisted';
    case Public = 'public';
}

Enum with Translator

Generate enums with automatic translation support:

<?php

namespace App\Enums;

use GaiaTools\TypeBridge\Attributes\GenerateEnum;

#[GenerateEnum(hasTranslator: true)]
enum UserRole: string
{
    case Admin = 'admin';
    case User = 'user';
    case Guest = 'guest';
}

Custom Output Format per Enum

Override the global output format for specific enums:

<?php

namespace App\Enums;

use GaiaTools\TypeBridge\Attributes\GenerateEnum;

#[GenerateEnum(outputFormat: 'js')]
enum Priority: int
{
    case Low = 1;
    case Medium = 2;
    case High = 3;
}

Available Commands

# Publish config with smart detection
# Automatically detects TypeScript/JavaScript and your i18n library!
php artisan type-bridge:publish

Generate Enums

# Generate enums using the default format from config (ts or js)
php artisan type-bridge:enums

# Generate enums explicitly as JavaScript
php artisan type-bridge:enums --format=js

# Generate enums explicitly as TypeScript
php artisan type-bridge:enums --format=ts

Generate Translations

# Generate translations for a locale (outputs to resources/js/locales/generated)
php artisan type-bridge:translations en

# Generate translations as JSON instead of TS (json|js|ts)
php artisan type-bridge:translations en --format=json

# Generate "flat" translation keys instead of nested
php artisan type-bridge:translations en --flat

Translation output examples (for locale en):

  • TypeScript (resources/js/locales/generated/en.ts)
// !!!!
// This is a generated file.
// Do not manually change this file
// !!!!

export const en = {
    common: {
        ok: "OK",
        cancel: "Cancel",
    }
} as const;

export type en = typeof en;

Publish Translator Utilities (frontend helpers)

These reusable helpers are required by the generated enum translators. Run once to publish them into your frontend source tree.

# Publishes:
# - composables/useTranslator.(ts|js)
# - lib/createEnumTranslationMap.(ts|js)
# - lib/translators.(ts|js)
php artisan type-bridge:publish-translator-utils [--force]

Defaults (can be changed in config/type-bridge.php → enum_translators.*):

  • utils_composables_output_path: resources/js/composables
  • utils_lib_output_path: resources/js/lib

The file extensions follow your type-bridge.output_format (ts by default).

Generate Enum Translators

Generate per-enum translator composables/functions that map enum values to translated labels using your configured i18n library.

# Uses the global output format (ts|js)
php artisan type-bridge:enum-translators

# Force a specific format
php artisan type-bridge:enum-translators --format=ts
php artisan type-bridge:enum-translators --format=js

# Dry-run: discover candidates and show eligibility without writing files
php artisan type-bridge:enum-translators --dry

By default files are written to:

  • resources/js/composables/generated (config: enum_translators.translator_output_path)

Dry-run output columns:

  • Enum: FQCN of the PHP enum
  • Prefix: Translation key prefix that will be used
  • In FE generation set: Whether this enum is part of your frontend enums set
  • Has translations: Whether any translations exist for that prefix

Only enums that are both in the FE generation set and have translations are eligible for generation.

Global translation engine setup (app.ts/js)

The generated translators call a global engine. You must configure it once during your app bootstrapping by calling configureTranslationEngine.

TypeScript (app.ts / main.ts / app.tsx)

// i18next (also works for react-i18next)
import i18n from '@/i18n';
import { configureTranslationEngine } from '@/composables/useTranslator';

configureTranslationEngine({
  t: (key: string) => i18n.t(key),
});

// React + react-i18next note:
// Prefer using the shared i18next instance you initialize for your app (as above).
// If you export that instance from your i18n setup file, this works in both React and non-React code.

// vue-i18n
import { createApp } from 'vue';
import { createI18n } from 'vue-i18n';
import App from './App.vue';
import { configureTranslationEngine } from '@/composables/useTranslator';

const i18nVue = createI18n({ /* ... */ });
const app = createApp(App).use(i18nVue);

// After vue-i18n is registered
configureTranslationEngine({
  t: (key: string) => i18nVue.global.t(key) as string,
});

app.mount('#app');

JavaScript (app.js / main.js)

// i18next (also works for react-i18next)
import i18n from '@/i18n';
import { configureTranslationEngine } from '@/composables/useTranslator';

configureTranslationEngine({
  t: (key) => i18n.t(key),
});

// vue-i18n
import { createApp } from 'vue';
import { createI18n } from 'vue-i18n';
import App from './App.vue';
import { configureTranslationEngine } from '@/composables/useTranslator';

const i18nVue = createI18n({ /* ... */ });
const app = createApp(App).use(i18nVue);

configureTranslationEngine({
  t: (key) => i18nVue.global.t(key),
});

app.mount('#app');

Once configured, any generated translator like useStatusTranslator() will use the global engine automatically:

import { useTranslator } from '@/composables/useTranslator';
import { createEnumTranslationMap } from '@/lib/createEnumTranslationMap';
import { Status } from '@/enums/generated/Status';

const translations = createEnumTranslationMap(Status, 'Status');
const tStatus = useTranslator(translations); // uses the globally configured engine

Using the translator utilities

After publishing the utilities and generating translators, you can translate enum values in the frontend.

Global engine configuration (once at app bootstrap) is required. See “Global translation engine setup” above. You can also use the lightweight wrappers from lib/translators:

// Vue i18n helper
import { createI18n } from 'vue-i18n';
import { createVueI18nTranslator } from '@/lib/translators';
import { configureTranslationEngine } from '@/composables/useTranslator';

const i18n = createI18n({ /* ... */ });
configureTranslationEngine(createVueI18nTranslator(i18n));

Manual configuration using i18next works similarly:

import i18n from '@/i18n';
import { configureTranslationEngine } from '@/composables/useTranslator';

configureTranslationEngine({ t: (key) => i18n.t(key) });

Example: translating a generated enum

If you generated the Status enum and have translations under the Status.* namespace, you can build a translator on the fly:

import { Status } from '@/enums/generated/Status';
import { createEnumTranslationMap } from '@/lib/createEnumTranslationMap';
import { useTranslator } from '@/composables/useTranslator';

const statusMap = createEnumTranslationMap(Status, 'Status');
const tStatus = useTranslator(statusMap);

tStatus(Status.Active); // → "Active"

// Build select options
const options = tStatus.options();
// → [{ value: 'active', label: 'Active' }, ...]

// Check if a value has a translation mapping
const hasPending = tStatus.has(Status.Pending); // true/false

You can also override the engine for a specific translator call:

const custom = useTranslator(statusMap, { t: (k) => myCustomFn(k) });

Notes and configuration

  • Configure your target i18n library via type-bridge.i18n_library (supports i18next and vue-i18n).

  • Control where generated translator files and utilities are written via enum_translators.* keys in config/type-bridge.php:

    • translator_output_path
    • utils_composables_output_path / utils_composables_import_path
    • utils_lib_output_path / utils_lib_import_path
  • The generators respect type-bridge.output_format for TS/JS.

  • JavaScript (resources/js/locales/generated/en.js)

// !!!!
// This is a generated file.
// Do not manually change this file
// !!!!

export const en = {
    common: {
        ok: "OK",
        cancel: "Cancel",
    }
};
  • JSON (resources/js/locales/generated/en.json)
{
  "common": {
    "ok": "OK",
    "cancel": "Cancel"
  }
}

GenerateEnum Attribute Options

The #[GenerateEnum] attribute accepts the following options:

  • requiresComments (bool): Include PHPDoc comments in generated output
  • hasTranslator (bool): Generate a translator for the enum
  • outputFormat (string|null): Override global output format ('ts' or 'js')

License

MIT