gettext/php-scanner
Scan PHP source to extract gettext translations for use with gettext/gettext. Supports multiple domains, default domain selection, and extracting translator/i18n comments. Produces Translations you can export to .po files with generators like PoGenerator.
Install the package:
composer require gettext/php-scanner
Basic extraction script (artisan command or standalone script):
use Gettext\Scanner\PhpScanner;
use Gettext\Generator\PoGenerator;
use Gettext\Translations;
$scanner = new PhpScanner(
Translations::create('messages') // Default domain
);
$scanner->extractCommentsStartingWith('i18n:', 'Translators:');
$scanner->scanFile(base_path('app/Http/Controllers/AuthController.php'));
$generator = new PoGenerator();
$generator->generateFile(
$scanner->getTranslations()['messages'],
resource_path('lang/messages.po')
);
First use case:
i18n: comments above translatable strings in your PHP files:
// i18n: Welcome to our platform!
echo __('Welcome to our platform!');
messages.po.Where to look first:
messages, validation).$scanner = new PhpScanner(
Translations::create('messages'),
Translations::create('validation')
);
$scanner->setDefaultDomain('messages'); // Fallback for unassigned strings
messages.po → resources/lang/en/messages.php).i18n: or Translators: comments to annotate strings.// i18n: This is a translatable string with %s placeholder
$user->greeting = sprintf(__('Hello, %s!'), $name);
phpcs rule to enforce these comments in your codebase.sprintf/printf patterns and add php-format flags to .po files..po:
msgid "User %s logged in"
msgstr ""
"php-format": "sprintf"
# Run only on changed PHP files
git diff --name-only HEAD~1 HEAD | grep '\.php$' | xargs -I{} php artisan scan:translations {}
{{-- i18n: Welcome back, {name}! --}}
<h1>{{ __('Welcome back, ') . $name . '!' }}</h1>
laravel-blade-compiler to convert Blade to PHP before scanning.Create a custom command (php artisan make:command ScanTranslations):
use Gettext\Scanner\PhpScanner;
use Gettext\Generator\PoGenerator;
class ScanTranslations extends Command {
protected $signature = 'scan:translations {--path= : Path to scan}';
protected $description = 'Scan PHP files for translatable strings';
public function handle() {
$scanner = new PhpScanner(Translations::create('messages'));
$scanner->extractCommentsStartingWith('i18n:');
$scanner->scanFile($this->option('path') ?: app_path('*'));
$generator = new PoGenerator();
$generator->generateFile(
$scanner->getTranslations()['messages'],
resource_path('lang/messages.po')
);
$this->info('Translations scanned and saved!');
}
}
Bootstrap scanning in AppServiceProvider:
public function boot() {
$this->scanTranslations();
}
protected function scanTranslations() {
$scanner = new PhpScanner(Translations::create('messages'));
$scanner->extractCommentsStartingWith('i18n:');
foreach (glob(app_path('*.php')) as $file) {
$scanner->scanFile($file);
}
// Save translations (e.g., via queue job)
}
Add a post-merge hook to auto-scan:
# .git/hooks/post-merge
#!/bin/bash
php artisan scan:translations --path=$(git diff --name-only HEAD~1 HEAD | grep '\.php$')
Example GitHub Actions workflow:
name: Scan Translations
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- run: composer install -n
- run: php artisan scan:translations --path=app/Http
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "chore: Update translations from scan"
False Negatives with Dynamic Strings
concat($var1, $var2)).// i18n: Concatenated string: {var1} {var2}
echo $var1 . ' ' . $var2;
Blade Template Limitations
.blade.php) are not parsed natively.laravel-blade-compiler).{{-- i18n: Submit button --}}
<button>{{ __('Submit') }}</button>
PHP 8.4+ Features
Comment Parsing Quirks
// i18n:) and avoid nested quotes:
// i18n: "Hello" (with quotes)
Performance with Large Codebases
--path to limit scanning (e.g., app/Http only).Domain Mismatches
.po file if domains are misconfigured.defaultDomain and validate domains in your scanner config.Enable Verbose Output
nikic/php-parser’s debug mode to inspect parsed code:
$scanner->setParserDebug(true); // Hypothetical; check latest API
Inspect Parsed AST
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$ast = $parser->parse(file_get_contents($file));
var_dump($ast); // Inspect nodes
Test with Simple Files First
AuthController.php) to validate extraction before scaling.Validate .po Output
msgfmt to check for syntax errors:
msgfmt --statistics locales/messages.po
How can I help you explore Laravel packages today?