Laravel package to manage, sync, and analyze JSON translation files.
- 🔍 Scan for translation keys in your codebase
- 📊 Statistics on translation progress
- 🔄 Sync translations with source files
- 🧹 Clean and sort translation files
- 🔧 Artisan commands for CLI management
Lingo works with Laravel's JSON translation files (not PHP array files). These files are located at lang/{locale}.json:
{
"Hello": "Halo",
"Welcome": "Selamat Datang",
"Save": "Save"
}Lingo determines whether an item is translated or not by comparing the key and value:
- ✅ Translated → key ≠ value (e.g.,
"Hello": "Halo") ⚠️ Untranslated → key = value (e.g.,"Save": "Save")- 🗑️ Empty → value is empty string (e.g.,
"Hello": "")
Since PHP automatically overwrites duplicate keys during json_decode(), Lingo uses regex on raw JSON content to detect duplicates before parsing:
// Find all "key": patterns in raw JSON
preg_match_all('/"([^"]+)"\s*:/', $jsonContent, $matches);The lingo:sync command and sync() method work by scanning your source code to find translation keys. The scanning process uses 4 regex patterns to detect Laravel translation function calls:
| Pattern | Detected Examples |
|---|---|
__('...') |
__('Hello'), __("Welcome") |
trans('...') |
trans('Save'), trans("Cancel") |
@lang('...') |
@lang('Submit') (Blade directive) |
Lang::get('...') |
Lang::get('Delete') |
Scanning process:
- Resolve path → Relative paths are resolved via
base_path(), absolute paths are used directly if they exist - Recursive scan → For directories, all
.phpfiles are scanned recursively usingRecursiveIteratorIterator - Extract keys → Each file's content is matched against the 4 regex patterns above
- Deduplicate → All found keys are deduplicated using
array_unique()
lingo:sync is the most powerful command. Here's how it works:
Source Files (*.php) Translation File (lang/id.json)
┌─────────────────┐ ┌─────────────────────────┐
│ __('Hello') │ │ "Hello": "Halo" │
│ __('Welcome') │ ──scan──▶ │ "Goodbye": "Selamat..."│
│ __('New Key') │ │ │
└─────────────────┘ └─────────────────────────┘
│ │
▼ ▼
Found keys: Existing keys:
[Hello, Welcome, New Key] [Hello, Goodbye]
│ │
└──────────────┬─────────────────────┘
▼
┌─── Compare ───┐
│ │
Missing keys Unused keys
[New Key] [Goodbye]
│ │
▼ ▼
Add "New Key":"New Key" Remove "Goodbye"
│ │
└───────┬───────┘
▼
Final: lang/id.json
┌─────────────────────────┐
│ "Hello": "Halo" │
│ "New Key": "New Key" │
│ "Welcome": "Welcome" │
└─────────────────────────┘
Steps:
- Scan all given paths (default:
resources/views) - Collect all translation keys found in source code
- Compare with existing keys in the JSON file
- Add missing keys (value = key, so they are easily identified as untranslated)
- Remove keys that are no longer used in source code
- Save the updated JSON file (sorted alphabetically)
All commands accept either a locale code or a direct file path:
php artisan lingo:check id # → Searches: lang/id.json, resources/lang/id.json
php artisan lingo:check lang/id.json # → Used directly as file pathFile search order:
base_path("lang/{locale}.json")lang_path("{locale}.json")resource_path("lang/{locale}.json")
composer require kanekescom/laravel-lingoAll commands accept an optional locale argument. If omitted, defaults to config('app.locale').
# Check for issues (duplicates, untranslated)
php artisan lingo:check # Uses app locale
php artisan lingo:check id # Specify locale
# Clean translation file (remove duplicates, empty, sort)
php artisan lingo:clean # Uses app locale
php artisan lingo:clean id # Specify locale
php artisan lingo:clean id --keep-empty # Keep empty values
# Show translation statistics
php artisan lingo:stats # Uses app locale
php artisan lingo:stats id --detailed # With samples
# Sort keys alphabetically
php artisan lingo:sort # Uses app locale
php artisan lingo:sort id --desc # Z-A order
# Sync with source files (find __(), trans(), @lang() calls)
php artisan lingo:sync # Scan resources/views (default)
php artisan lingo:sync id --path=resources/views # Single path
php artisan lingo:sync id --path=resources/views --path=app/Filament # Multiple paths
php artisan lingo:sync id --path=app/Http/Controllers/HomeController.php # Single file
php artisan lingo:sync id --add # Only add missing keys
php artisan lingo:sync id --remove # Only remove unused keys
php artisan lingo:sync id --dry-run # Preview changesNote: All paths are relative to your application root. Both files and directories are supported.
use Kanekescom\Lingo\Facades\Lingo;
// Load by locale and manipulate
Lingo::locale('id')->sortKeys()->save();
Lingo::locale('id')->clean()->save();
// Sync with source files
Lingo::locale('id')->sync()->save(); // Default: resources/views
Lingo::locale('id')->sync('app/Filament')->save(); // Single folder
Lingo::locale('id')->sync(['resources/views', 'app/Filament'])->save(); // Multiple paths
Lingo::locale('id')->sync('app/Http/Controllers/HomeController.php')->save(); // Single file
// Get data
$stats = Lingo::locale('id')->stats();
$untranslated = Lingo::locale('id')->onlyUntranslated()->get();
// Create from array (overwrites file)
lingo(['Hello' => 'Halo'], 'id')->save();
// Using helper with sync (cleaner syntax)
lingo()->sync(['resources/views', 'app/Filament'])->to('id')->save();
// Merge with existing translations
Lingo::locale('id')->merge(['Hello' => 'Halo'])->save();
// Without locale - fallback to app()->getLocale()
lingo(['Hello' => 'Halo'])->save();| Method | Description |
|---|---|
Lingo::locale('id') |
Load by locale → lang/id.json |
Lingo::fromFile($path) |
Load from custom file path |
Lingo::make($arr, $locale) |
Create from array with optional locale |
lingo($arr, 'id') |
Helper: create from array with locale |
| Method | Description |
|---|---|
to($locale) |
Set target locale for save() |
sync($paths) |
Sync with source files (accepts string, array, or null for default views). Supports files and directories. |
sortKeys($asc) |
Sort keys alphabetically (default: A-Z) |
clean() |
Remove empty values and sort keys |
merge($arr) |
Merge with another array |
add($keys) |
Add keys if not present |
remove($keys) |
Keep only keys in list |
removeEmpty() |
Remove empty values |
onlyUntranslated() |
Filter to untranslated items |
onlyTranslated() |
Filter to translated items |
transform($fn) |
Transform translations with callback |
tap($fn) |
Inspect translations without modifying |
| Method | Description |
|---|---|
save($path) |
Save to file (path optional) |
toJson() |
Export as JSON string |
get() |
Get translations array |
toArray() |
Alias for get() |
stats() |
Get translation statistics |
count() |
Get number of translations |
isEmpty() |
Check if translations empty |
isNotEmpty() |
Check if translations not empty |
composer testPlease see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.