What is String Extraction?
String extraction is the step that collects translatable strings from source code into whatever the translation workflow consumes: lang files, a POT template, or a TMS. An extractor statically scans the code for translation calls, __() and trans_choice() in Laravel, _() in gettext projects, and records each key with its file and line.
gettext's xgettext is the classic implementation, and every ecosystem has grown equivalents. Extraction keeps code as the source of truth for which keys exist: run it in CI and you can diff the extracted set against your translation files, catching keys used in code but missing from translations, and orphaned translations no code references anymore.
The hard cases are dynamic keys, __('errors.' . $code) defeats static analysis, and strings that should be translatable but are not wrapped in any call at all. The first needs conventions or annotations; the second is the hardcoded-string problem, which needs a scanner that looks for raw literals rather than translation calls.
Extracting keys from a Laravel project
Laravel ships no extraction command. lang:publish copies the framework's own lang files into your project; it does not scan your code. Packages and translation tools fill the gap, but a basic extractor is short enough to write and understand: find every call to __(), trans(), trans_choice(), @lang or @choice with a literal first argument, then compare the keys with your lang files.
The version below runs inside an Artisan command or tinker. It records where each key is used and reports the keys that the target locale does not define itself.
use Illuminate\Support\Facades\Lang;
use Symfony\Component\Finder\Finder;
$pattern = '/(?:__|trans|trans_choice|@lang|@choice)\(\s*([\'"])((?:(?!\1).)+)\1/';
$used = [];
foreach (Finder::create()->files()->in([app_path(), resource_path('views')])->name('*.php') as $file) {
preg_match_all($pattern, $file->getContents(), $matches);
foreach ($matches[2] as $key) {
$used[$key][] = $file->getRelativePathname();
}
}
$missing = array_filter(array_keys($used), fn (string $key) => ! Lang::hasForLocale($key, 'es'));
What a regex extractor misses
Static extraction only sees literals. Keys built at runtime, such as __("alerts.types.{$type}") or __($key), either do not match or match as a partial key that exists nowhere; running the script above on a real project surfaces exactly these. Escaped quotes inside a key cut it short, and unusual formatting can slip past a simple pattern. For PHP files, a real parser such as nikic/php-parser, which most Laravel projects already have installed as a dependency of tinker, removes most of these misreadings.
Some keys never appear in your code at all. The validator resolves validation.required and the other rule messages by itself, the framework's pagination views use pagination.previous and pagination.next, and authentication code reads auth.failed and the passwords file. Front-end code may read translations through its own helpers. Treat keys in those groups as used.
Using the results
Extraction produces two lists. Keys used in code but missing from a locale will show raw keys or fallback text to users, so fail the build on them. Keys defined in lang files but never found in code are candidates for deletion rather than automatic deletions, because of the implicit and dynamic keys above.
In projects that use natural-language JSON keys, a key missing from the source locale's file is not an error, since the key already is the English text; check the target locales only, as the script does. Committing the extracted key list, or publishing it as a build artifact, makes a pull request show which strings it adds or removes, so new text reaches translators before the release instead of after it.
Extraction only finds strings that are already wrapped in a translation call. Text typed straight into a Blade template is invisible to it; that is the hardcoded-string problem, and it needs a scanner that looks for raw literals instead. Running both in CI, one for missing keys and one for unwrapped text, covers the two ways untranslated text reaches users.