yuriidiachuk / laravel-typed-i18n
Generate TypeScript types from Laravel translation files
Package info
github.com/YuriyDyachuk/laravel-typed-i18n
pkg:composer/yuriidiachuk/laravel-typed-i18n
Fund package maintenance!
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
This package is auto-updated.
Last update: 2026-06-19 11:49:03 UTC
README
Turn your Laravel translation files into a TypeScript declaration file, so your frontend gets
autocomplete on translation keys and type-checked parameters, including a required
count on pluralized strings. Zero runtime: the package emits types only and stays out of the
way of whatever trans/$t/__ helper you already use.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require yuriidiachuk/laravel-typed-i18n --dev
Optionally publish the config:
php artisan vendor:publish --tag="typed-i18n-config"
Usage
php artisan typed-i18n:generate
Given lang/en/messages.php:
return [ 'welcome' => 'Hello :name', 'cart' => [ 'items' => '{1} :count item|[2,*] :count items', ], ];
and lang/en.json:
{ "Save": "Save" }
the command writes (default resources/js/types/translations.d.ts):
// translations.d.ts // GENERATED by laravel-typed-i18n. Do not edit by hand. export interface Translations { 'Save': {}; 'cart.items': { count: number }; 'messages.welcome': { name: string | number }; } export type TranslationKey = keyof Translations; export type EmptyParamKeys = { [K in TranslationKey]: keyof Translations[K] extends never ? K : never; }[TranslationKey];
Wiring it to your translator
The Translations interface maps each key to its required params. Wrap your existing helper once
to get full type safety and autocomplete:
import type { Translations, TranslationKey, EmptyParamKeys } from '@/types/translations'; export function t<K extends EmptyParamKeys>(key: K): string; export function t<K extends Exclude<TranslationKey, EmptyParamKeys>>(key: K, params: Translations[K]): string; export function t(key: TranslationKey, params?: Record<string, unknown>): string { return translate(key, params); // delegate to your runtime (laravel-vue-i18n, i18next, Inertia, etc.) } t('messages.welcome', { name: 'Yurii' }); // ✅ name required & autocompleted t('messages.welcome'); // ❌ TS error: missing params t('cart.items', { count: 3 }); // ✅ count: number required on plural keys t('Save'); // ✅ no params
What it parses
lang/{locale}/*.phpgroup files becomemessages.auth.login(dot notation, nested arrays flattened)lang/{locale}.jsonuses the phrase as the key, verbatimlang/vendor/{package}/{locale}/*.phpbecomespackage::group.key(toggle withinclude_vendor):placeholdertokens become required params, case-normalized (:name/:Name/:NAMEare one param)- pluralized strings (
apple|apples,{0}...|[2,*]...) get a requiredcount: number
Locale drift detection
The reference locale (config default_locale, falling back to app.fallback_locale) defines the
types. Every other locale is compared against it and missing or extra keys are reported when you
run the command:
WARN Locale [de] is missing 2 key(s) present in [en]: cart.items, messages.welcome
Keeping types in sync
The generated file is a build artifact. Regenerate it whenever translations change instead of editing it by hand. A few common ways to wire that in:
-
Run the command before your frontend build, for example in a
composerornpmscript. -
Add it to a pre-commit hook so the file never lags behind the lang files.
-
Run
typed-i18n:generate --checkin CI. It compares the committed file against freshly generated types and exits non-zero (without writing) when they differ, so a translation changed without regenerating fails the build:php artisan typed-i18n:generate --check
Whether you commit the file or generate it on the fly is up to you. Committing it keeps diffs visible in review; generating it on the fly keeps it out of version control.
Configuration
return [ 'lang_path' => null, // null uses lang_path() 'default_locale' => null, // null uses config('app.fallback_locale') 'locales' => null, // null auto-discovers locales from the lang path 'output' => null, // null uses resource_path('js/types/translations.d.ts') 'include_vendor' => true, ];
CLI options: --locale=, --output=, and --check (verify only, never writes).
Testing
composer test
License
The MIT License (MIT). Please see License File for more information.