kkomelin/laravel-translatable-string-exporter
A Laravel package that scans your app for translatable strings and exports them into ready-to-edit language files, streamlining localization workflows with an Artisan command and support for common translation formats.
This package scans your Laravel source for translatable string calls and exports them to a JSON language file — ideal for the JSON-based translation workflow (__('Hello') → lang/en.json) rather than PHP array files.
Install as a dev dependency so it never ships to production:
composer require --dev kkomelin/laravel-translatable-string-exporter
Version 1.26.0 adds Laravel 13 support, so 1.26.0+ works across the modern framework line (Laravel 9 through 13). If you're on the newest Laravel release and the package refuses to install, you're almost certainly on an older pin — check composer show kkomelin/laravel-translatable-string-exporter and bump to ^1.26.
Publish the config (optional but recommended):
php artisan vendor:publish --provider="KKomelin\TranslatableStringExporter\Providers\ExporterServiceProvider"
This gives you config/translatable.php — the first place to look if defaults don't match your project layout.
First run:
php artisan translatable:export en
This writes/updates resources/lang/en.json (or lang/en.json on Laravel 9+, depending on package version and whether the app uses the newer lang path). Open the generated file: every literal you used with __(), trans(), @lang, and trans_choice() should appear as a "key": "key" pair.
First use case: bootstrap an English (or source-locale) JSON file from an existing app, then hand that file to a translator or translation-management platform. Confirm the command is registered with php artisan list | grep translatable before digging into config — it's a one-command tool, and most confusion comes from it scanning fewer paths than expected.
Source-locale-first workflow. Treat the exporter as the single source of truth for your source language. Export en, commit the diff, then hand en.json to translators. Never hand-edit the source locale file after you've adopted this — regenerate it and let translators work on the target locales.
CI guard against forgotten strings. The highest-value pattern: fail the build when a new __() call hasn't been exported.
php artisan translatable:export en
git diff --exit-code lang/en.json
Run this in pull-request CI. It catches strings that were added in code but never made it into the translation file. Pair it with --exclude for paths that legitimately shouldn't be exported.
Pre-commit hook. For teams that don't want CI failures, run the export + git add lang/en.json in a pre-commit hook (Husky, captainhook, or a plain .git/hooks/pre-commit). Keeps the translation file in lockstep with code in the same commit.
Scoping the scan. Default scan targets are typically app and resources/views. Extend via config (include-paths/exclude-paths style keys) or per-invocation flags:
# Add a package/domain directory that contains translations
php artisan translatable:export en --include=packages/billing/src
# Skip vendored or generated views you don't own
php artisan translatable:export en --exclude=resources/views/vendor
Prefer config file entries over CLI flags when the same set applies to every developer and every CI run.
Multi-locale layout. Run once per locale you support. The exporter creates a file per locale; existing translations are preserved and missing keys are filled in with the key itself as a fallback. This makes it safe to re-run repeatedly as part of a release checklist.
Composer script. Wire it into composer.json for discoverability:
"scripts": {
"translations:export": "php artisan translatable:export en"
}
Framework upgrade checklist item. Thanks to 1.26.0 adding Laravel 13 support, this package is no longer a blocker on major upgrades. When bumping Laravel, re-run the export immediately after the upgrade and diff — framework upgrades often touch vendor views and starter-kit files, and you want to confirm your exclusion list still matches the new directory structure.
TMS / POEditor / Lokalise integration. Because the output is a flat JSON map, it drops straight into any translation platform that speaks JSON. Typical loop: export source → upload to TMS → download target locales → commit. Keep the target-locale files out of the exporter's write path if your platform owns them, or make sure translators' edits won't be clobbered.
Only literal arguments are extracted. __('Hello') and trans('messages.welcome') are found; __($dynamicKey) and __("Hello $name") are not. If your app builds keys dynamically, those strings need to be handled manually — nothing in the exporter's output will warn you.
Concatenation silently truncates. __('Hello ' . $name) is likely captured as just 'Hello ' (or skipped). Keep translation keys as single literals; use :name placeholders instead of concatenating.
Stale keys are not pruned automatically. The export merges into the existing file rather than replacing it, so translations for removed strings linger. Periodically diff your exported keys against keys actually present in code and clean up — otherwise your JSON grows unboundedly and translators keep getting billed for dead strings.
False positives from comments and dead code. Regex/lexer-based extraction doesn't know whether a __() call is reachable. Old commented-out blocks and unused views can inject keys into the file. Exclude archived paths and periodically review the diff.
Exclusion patterns are path-based and OS-sensitive. Use forward slashes in exclude entries even on Windows. Relative paths are resolved against the project root — test exclusions by running the export and checking that removed directories truly disappear from the diff.
Laravel 9+ lang path. Laravel moved the default translation directory from resources/lang to lang. Older releases of this package wrote to resources/lang unconditionally. If your keys land in the wrong place, check your installed version and any lang_path/output-path config, or upgrade the package. This is one of the most common "why isn't it working" reports.
Version compatibility and older Laravel. 1.26.0's headline change is Laravel 13 support — no code changes or config changes required on your end, and no deprecations. The practical consequence is that the package's support window now spans Laravel 9 through 13, so pin to a caret range that stays current (^1.26) rather than a hard pin, or you'll be the one debugging a solver conflict on the next framework bump instead of pulling the fix.
JSON escaping edge cases. Quotes, backslashes, and unicode in source strings round-trip through JSON. Verify strings with embedded double quotes survive an export/import cycle, and be cautious with keys containing trailing whitespace — they'll look identical in your editor but break lookups.
Sorting stabilizes diffs. Enable key sorting in config (or the CLI equivalent) and keep it on. Without it, the order of keys tracks directory traversal, producing noisy diffs and merge conflicts on every run.
Watch what @lang / @choice do. Blade directives are supported, but custom Blade components, app-specific translation macros (e.g. a __t() helper), and third-party packages with their own helpers won't be picked up. Wrap those in standard __() calls at the boundary, or extend the scan manually.
Don't run on production. It's a build-time tool that rewrites files. Keep it in require-dev, and never invoke it from a deploy script that could overwrite translator edits with the source-locale fallback values.
Extension point worth knowing: custom or non-standard translation functions are the most frequent ask. If your team uses a wrapper, either standardize on __()/trans() or add the wrapper's directory to includes and accept that the wrapper call itself won't be recognized — the package targets the framework's built-in helpers.
How can I help you explore Laravel packages today?