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

Translation Bundle Laravel Package

kilik/translation-bundle

Kilik Translation Bundle is a Symfony package for managing translations in a database through a web interface. It supports editable message catalogs, import/export of translation files, and smoother localization workflows without hand-editing YAML or XLIFF resources.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Confirm the version gate before anything else. Release 1.0.0 changes the constraint set: it adds Symfony 5.4 compatibility and drops Symfony < 5 entirely. That is a legitimate major bump (dropping platform support), but it means the pre-1.0 line is now a dead end and the 1.x line is the only one that will ever boot on a modern app. Pin it explicitly so a future resolution surprise can't move you:

composer require --dev kilik/translation-bundle:^1.0

If Composer refuses on a Symfony 4.4 project, that is the intended behaviour — do not force it with --ignore-platform-reqs. Stay on 0.x and accept it is unmaintained, or move the project forward first.

2. Note what this bundle is not. It is a Symfony bundle (KilikTranslationBundle), not a Laravel package. On a Laravel codebase there is no config/bundles.php and nothing here will autoload. Laravel's equivalent surface is already native — lang/, php artisan lang:publish, and community packs like laravel-lang/lang for shipped locale files or barryvdh/laravel-translation-manager for a UI. The only reason to read further is if you are working in a Symfony app (or a Laravel repo that also contains one), or you are porting the spreadsheet round-trip idea.

3. Register the bundle manually. There is no Flex recipe for a 4-star bundle, so nothing is auto-wired. Edit config/bundles.php and scope it to dev/test so it never boots in prod:

return [
    // ...
    Kilik\TranslationBundle\KilikTranslationBundle::class => ['dev' => true, 'test' => true],
];

4. Look at these two files first, in this order. The bundle is small, so read the source rather than the (thin) docs:

  • vendor/kilik/translation-bundle/src/DependencyInjection/Configuration.php — the authoritative list of config keys and defaults. Do not guess the config alias; derive it from getAlias() in vendor/kilik/translation-bundle/src/DependencyInjection/*Extension.php (typically kilik_translation).
  • vendor/kilik/translation-bundle/src/Command/ — the real surface area. Command names are the API.

The 1.0.0 notes describe platform support only; they tell you nothing about the command surface. Assume the commands and options you find in src/Command/ are the commands and options — the release notes will not warn you if one changed.

5. Discover the commands instead of trusting a blog post.

bin/console list --raw | grep -i trans

You are looking for the import and export pair (roughly kilik:translation:export / kilik:translation:import). Confirm the exact names and options with bin/console help <command> before scripting anything.

6. Configure a scratch working directory.

# config/packages/kilik_translation.yaml
kilik_translation:
    # keys confirmed against Configuration.php — not guessed
    default_locale: en
    locales: [en, fr, de]
    format: xlsx          # or csv, depending on your exporter
    path: '%kernel.project_dir%/var/translation-exchange'

Keep var/translation-exchange/ out of git. The exchange file is a courier artifact, not a source of truth.

First use case. The one that justifies the install: you have ~800 keys across messages.fr.yaml and validators.fr.yaml, and a freelance translator who will not touch YAML. Export to a spreadsheet, let them fill a column, import back, review the diff.

bin/console kilik:translation:export --locale=fr
# → var/translation-exchange/messages.fr.xlsx
# ... translator fills the column, sends it back ...
cp ~/Downloads/messages.fr.xlsx var/translation-exchange/messages.fr.xlsx
bin/console kilik:translation:import --locale=fr
git diff translations/

Everything after this step is about making that round-trip safe.

Implementation Patterns

Treat export/import as a lossy courier, not as storage. The YAML/XLF files in translations/ stay authoritative. Because import rewrites files, always import on a clean working tree:

git diff --quiet translations/ || { echo "commit or stash first"; exit 1; }
bin/console kilik:translation:import --locale=fr
git diff --stat translations/

If the diff is a full-file rewrite (reordered keys, changed quoting), discard it and fix the config before continuing — a noisy diff makes review impossible and will get reverted by the next person.

Scope the round-trip per domain and per locale. Never export "everything" in one shot. Domains are the natural unit of review (messages, validators, security), and per-locale keeps files small enough for a translator to actually finish. Drive it from a small shell script so it is repeatable:

#!/usr/bin/env bash
set -euo pipefail
for locale in fr de; do
  for domain in messages validators; do
    bin/console kilik:translation:export --locale="$locale" --domain="$domain"
  done
done

Wire the "find missing keys" half into CI, not into a human's calendar. The export command surfaces untranslated keys; use that as a gate on new keys so gaps never accumulate. Pair it with Symfony's native extractor, which you already have — and which is the part of your pipeline that will still be maintained in two years:

bin/console translation:extract --format=yaml --force fr
bin/console translation:extract --format=yaml --force de
git diff --exit-code translations/ || \
  echo "::warning::translation files changed — new keys need translating"

Treat translation:extract as the writer and kilik:translation:export as the reporting/transport layer. Do not try to make one do the other's job.

Use import as a merge, but verify the semantics yourself. The critical question on any import command is replace the file vs merge into existing keys. The 1.0.0 notes give you no help here, so test it deliberately before rolling out to a team:

# 1. snapshot, 2. delete one key from the import file, 3. import, 4. inspect
cp translations/messages.fr.yaml /tmp/before.yaml
bin/console kilik:translation:import --locale=fr
diff /tmp/before.yaml translations/messages.fr.yaml

If a key dropped from the spreadsheet silently disappears from the YAML, translators will delete keys by leaving cells blank. Know which behaviour you have and instruct the translator accordingly (fill vs delete).

Guard the import in a command wrapper if your team has more than one person touching translations. A tiny app-level console command that (a) validates the file is UTF-8, (b) asserts row count didn't drop by more than N%, and (c) runs translation:lint, then delegates to the bundle, is worth the 40 lines:

protected function execute(InputInterface $in, OutputInterface $out): int
{
    $rows = $this->countRows($in->getArgument('file'));
    if ($rows < $this->expectedMinimum) {
        $out->writeln('<error>Row count dropped unexpectedly — refusing to import.</error>');
        return Command::FAILURE;
    }
    return $this->getApplication()->find('kilik:translation:import')
        ->run($in, $out);
}

Version the exchange files if your translator works asynchronously. For a single contributor, commit the .xlsx under a translations-exchange/ directory and treat it as the handoff log — it makes "what did we send them?" answerable. For anything larger, use a real TMS (Loco, Phrase, Crowdin) via Symfony's native translation:push / translation:pull commands instead of file passing; that path is maintained and this bundle is not.

Extension points, if you need them. The bundle is small enough that the pragmatic extension is decoration, not inheritance: wrap the import/export commands with your own command and delegate to the bundle's via $this->getApplication()->find(...)->run(). Prefer this to subclassing internals — 1.0.0 was reached without a documented changelog, so you have no signal about which internals moved between 0.x and 1.x, and none about which will move again.

Gotchas and Tips

1.0.0 is a support-matrix release, not a revival. The entire changelog is "symfony 5.4 compatibility added" plus a compatibility floor. That is one commit's worth of information and it says nothing about bug fixes, deprecations resolved, or the state of the test suite. The upstream risk profile is unchanged: 4 stars, 0 dependents, one maintainer. Treat 1.0.0 as "the package now installs on Symfony 5.4" and nothing more. Verify it actually boots before building a workflow on it:

composer show kilik/translation-bundle
bin/console debug:container --parameter=kernel.bundles  # confirm it registered
bin/console cache:clear && bin/console about

The Symfony 5.4 target is also the ceiling of what has been verified. The release notes name 5.4 specifically. Symfony 6.x and 7.x are not claimed. Code from this era commonly hits removed ContainerBuilder APIs, @required setter injection deprecations, and non-nullable-parameter deprecations on PHP 8.2+. If bin/console list throws after install, that is the likely cause — decide fast whether you are patching a fork or walking away. Do not sink a day into it for a tool that saves two hours.

Check what the 1.0.0 constraint dragged in. A support-matrix bump often comes with a spreadsheet library bump or pin. After install:

composer why phpoffice/phpspreadsheet

A silently downgraded phpspreadsheet (or symfony/framework-bundle resolving lower than you expect) breaks other dev tooling in ways that surface weeks later. If Composer resolves anything downward, stop and read the solver output before proceeding.

Know what supersedes it. Symfony 5.4 — the very version 1.0.0 targets — ships translation:extract, translation:lint, and provider-backed translation:pull/translation:push. The only genuine gap this bundle fills is spreadsheet round-trip with a human translator who will not use a TMS. If that is not your situation, you do not need this package at all — translation:extract --force already finds missing keys, and it ships with the framework you are on.

Config alias and key names are the #1 first-hour time sink. Guessing kilik_translation: wrong yields Unrecognized option errors far from the cause. Read Configuration.php, then validate:

bin/console config:dump-reference kilik_translation

If that prints nothing, the alias is different — grep the extension class.

CSV/Excel encoding traps. If the format is CSV: a UTF-8 BOM makes Excel open accented characters correctly but injects \xEF\xBB\xBF into the first header cell, which will break header matching on import. Agree on a delimiter explicitly (; for French-locale Excel, , otherwise) and set it in config rather than relying on sniffing.

Whitespace and empty strings are not the same as missing. A cell containing a space, or an empty string, may be written as '' and pass lint while rendering as blank in the UI. Add a grep to your review step:

grep -nE "^\s*[a-z0-9_.]+:\s*(''|\"\")\s*$" translations/*.yaml

Nested keys and dot notation. messages.fr.yaml nests (user.profile.title:), and flat export formats flatten it. Import must unflatten back identically or your |trans calls silently miss. Verify with a nested key you can eyeball in a browser before trusting a large import.

Pluralization and ICU messages will be mangled by naive cell splitting. {count, plural, one {…} other {…}} contains commas and braces; CSV quoting often loses it. Test with one pluralized key before exporting 800. If it breaks, exclude those keys from the export and handle them by hand.

Cache is the other silent failure. After a successful import, a stale cache makes it look like nothing changed:

bin/console cache:clear
# or, in dev, just touch nothing and hard-refresh — but for staging, clear explicitly

File permissions. Import runs as your shell user and writes into translations/. In Docker setups where the web user owns those files, you will create root-owned YAML that breaks subsequent deploys. Run the import inside the same container/user that owns the tree.

Debugging approach that actually works on this bundle: the code is ~a few dozen small classes, so read the command's execute() and the exporter/importer service it calls, then var_dump at the point of write. There is no meaningful logging and no advertised test suite to lean on. Budget an hour for source reading on day one and you will spend minutes thereafter.

Exit criteria. Set a date to reconsider. If within a quarter you have done fewer than three round-trips, drop the bundle and keep translation:extract + translation:lint in CI — an unmaintained dev dependency is a liability with no payoff, and this one still has no dependents to carry it forward.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource