Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Localizator Laravel Package

amirami/localizator

Localizator is a Laravel package for managing localization files. It scans your application for translation strings, generates and updates language files, and provides a web interface for editing translations. Simplify multilingual Laravel projects with automatic scanning and convenient translation...

View on GitHub
Deep Wiki
Context7

Getting Started

Install (dev-only). Localizator is a build-time tool, not a runtime dependency. Keep it out of production:

composer require --dev amirami/localizator
php artisan vendor:publish --tag=localizator

The publish tag may be localizator or localizator-config depending on version — if the tag fails, run php artisan vendor:publish and pick the package from the interactive list.

Read the config first. Everything the tool does is driven by config/localizator.php. Before running anything, open it and confirm these four knobs:

Key What it controls Day-one advice
target_locales Which locale files get written Start with just ['en']
search_paths Directories scanned Confirm app/, resources/views/, routes/ are covered; add packages/ or modules if you use a monorepo layout
exclude / exclude_files Paths and filenames skipped Always include vendor, node_modules, storage, bootstrap/cache
patterns Regexes used to find translatable strings Defaults cover __(), trans(), @lang, trans_choice; extend for your own helpers

First run — on a branch, with a clean tree.

git checkout -b chore/extract-translations
git status          # must be clean so the diff is reviewable
php artisan localize

The command scans the configured paths, prints what it extracted, and writes/updates the locale files (JSON or PHP depending on config). Then git diff — that diff is the deliverable. Review it before committing; it is your first and best signal for false positives.

The first use case to target. The highest-value scenario is a project that was built with English literals inline — {{ __('Welcome back') }} scattered across 200 Blade files. Point Localizator at it, generate a single canonical lang/en.json, commit it, and you now have a translation backlog you can hand to a translator or pipe into a machine-translation step. Do not start by adding five locales; get one locale file correct and stable first, then fan out.

Implementation Patterns

Pattern 1: Extract → Review → Translate → Merge

The healthy loop separates three concerns that Localizator deliberately does not combine:

  1. Extract — php artisan localize regenerates the key set from source.
  2. Review — a human reads the git diff to reject comment matches, dynamic-key false positives, and duplicates.
  3. Translate — the resulting file goes to translators, Crowdin, or a machine-translation step. Localizator never translates; it only inventories.

Run extraction on a cadence (pre-release, or weekly in CI as a report), not on every save. Treat the generated file as a reviewed artifact, not a build output.

Pattern 2: JSON for prose, PHP for structured keys

The single most consequential config decision. Message strings that contain dots ("Please confirm the file name.") collapse into nested arrays when written to PHP files, which then can't be looked up as a flat key. Rule of thumb:

  • JSON files for UI copy and any free-form prose — keys stay literal.
  • PHP files only where you genuinely want auth.php, validation.php, passwords.php structure.

Mixing both in one project works, but decide per-domain and write it down, because a string that lands in the wrong format fails silently at runtime (__('some.key') returns the key itself).

Pattern 3: Extend patterns for your own conventions

Out of the box you get the framework's helpers. Most teams have one or two wrappers. Add them rather than rewriting call sites:

'patterns' => [
    // ... defaults ...
    '/\bt\(\s*[\'"](.+?)[\'"]\s*[,)]/',
    '/@translatable\(\s*[\'"](.+?)[\'"]\s*\)/',
],

Also point search_paths at anything the defaults miss: Livewire component classes, Blade components under resources/views/components, route files with ->name(__('...')), or an Inertia/Vue directory if you want to regex JS.

Pattern 4: CI guardrail for missing strings

The cheapest integration that pays for itself: fail the build when source has translatable strings that aren't in the locale file.

- name: Check translation coverage
  run: |
    php artisan localize
    git diff --exit-code lang/ || (echo "New untranslated strings found. Run: php artisan localize" && exit 1)

This forces developers to commit extracted strings in the same PR that introduces them. It's coarse (it will also fail on ordering churn — see sort_keys below), but it stops the "we'll extract before release" debt from accumulating.

Pattern 5: Sort keys early, once

If sort_keys is available in your version, enable it on the first run, before the file grows. Turning it on later rewrites the entire file in one commit and poisons git blame for every key. A stable sort order also makes the CI diff check above reliable — without it, map-ordering noise causes spurious failures.

Pattern 6: Incremental adoption in a legacy codebase

Don't try to extract a 50k-line project in one shot. Scope search_paths to a module at a time:

'search_paths' => [
    base_path('app/Modules/Billing'),
],

Localize billing, ship it, then add the next module. Each run only writes to its locale files, so the diffs stay reviewable and the translator batches stay small.

Pattern 7: Pair with a translation manager, not a translator script

Localizator's output is plain Laravel lang files — the exact input format for laravel-lang/* community translations and for most TMS imports (Crowdin, Lokalise, Phrase). Keep Localizator as the source of keys and let the TMS own the values; don't let both write to lang/.

Gotchas and Tips

It is regex, not an AST. This is the defining limitation and explains most surprises. Expect blind spots:

  • Concatenation: __('Hello ' . $name) — the pattern won't resolve the full string.
  • Multiline calls and heredocs.
  • Dynamic keys: __($message), __("errors.{$code}") — correctly skipped, but they are untranslated strings that will never appear in the diff. Audit these manually.
  • Escaped quotes inside single-quoted arguments can truncate the match.
  • Strings in comments or docblocks can be picked up as false positives. Skim the diff for these every run.

It adds keys; it generally doesn't curate them. Verify on a branch whether your version overwrites existing values or only appends missing keys — behaviour here matters enormously if you have hand-edited translations. Assume it will not remove stale keys either: when a string is deleted from source, its translation lingers. Periodic pruning is a manual job.

lang/ moved in Laravel 9+. Pre-9 projects use resources/lang/, newer ones use lang/ at the project root. If the command reports writing files but nothing changes, your lang_path in config is pointing at the wrong place. Check the package's Laravel version constraint against your framework version too.

Clean git tree, always. There's no reliable dry-run flag across versions. The git diff after a run is your only review surface. Running with uncommitted work means you can't tell extracted strings apart from your own edits.

Exclude generated and vendored code aggressively. Compiled Blade views land in storage/framework/views and will be re-scanned on every run — noisy, slow, and full of duplicated strings. Same for bootstrap/cache, vendor, node_modules, and any published package views you don't own.

Dotted keys collide and nest. In PHP lang files, __('auth.failed') is a nested lookup. If you also have a UI string that happens to be "auth.failed", one clobbers the other. JSON files dodge this entirely; that's the reason to prefer them for prose.

Performance scales poorly on huge trees. The scanner is a plain recursive file walk plus per-file regex. On a repo with tens of thousands of PHP/Blade files, expect minutes. Restrict search_paths rather than trying to tune it.

Pin the version and treat upgrades as review-worthy. NOASSERTION licensing means you should confirm the terms with your legal/procurement process before committing the dependency manifest — an unlabelled license is not the same as MIT.

It doesn't validate your translations. A key present in en.json but missing in de.json is invisible to this tool. Pair it with a coverage assertion (array_diff_key between locale files) if missing translations are a real production risk.

Extension points are narrow by design. Realistically you have two: the patterns regexes and the search_paths/exclude scoping. There's no plugin API for custom extraction logic. If you need AST-accurate extraction (for concatenations or dynamic keys), you're outside this package's scope and should look at a PHP-parser-based approach instead — accept that and use Localizator for the 85% it handles well.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment