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

Po Parser Laravel Package

sepia/po-parser

Sepia PO Parser is a PHP library for reading, writing, and updating GNU gettext .po translation files. It handles contexts, plural forms, comments, and multiline strings, making it useful for translation tools and programmatic localization workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — there is no Laravel service provider, so it is a plain library you wire up yourself:

composer require sepia/po-parser

First place to look is the source tree, in this order:

  • src/Sepia/PoParser/Parser.php — entry point; you hand it a source handler and get back a Catalog.
  • src/Sepia/PoParser/SourceHandler/ — FileSystem for files on disk, StringSource for parsing a string already in memory.
  • src/Sepia/PoParser/Catalog/Catalog.php — the collection of entries plus the PO header.
  • src/Sepia/PoParser/Catalog/Entry.php — one msgid/msgstr pair with comments, flags, references, and plural forms.
  • src/Sepia/PoParser/PoCompiler.php — turns a Catalog back into .po text.

Minimal read:

use Sepia\PoParser\Parser;
use Sepia\PoParser\SourceHandler\FileSystem;

$parser  = new Parser(new FileSystem(resource_path('lang/fr_FR/messages.po')));
$catalog = $parser->parse();

foreach ($catalog->getEntries() as $entry) {
    // $entry->getMsgId(), getMsgStr(), getMsgCtxt(), getFlags(), getMsgStrPlural()
}

The first use case worth building is a translation coverage report — parse a .po, count entries where msgstr is empty or the entry carries the fuzzy flag, and output the list of untranslated msgids. That exercises the whole API (parsing, entry inspection, flag handling) in about twenty lines and immediately tells you whether the library behaves the way you expect on your real catalogues.

Implementation Patterns

Read-only auditing. Parse one or many .po files and treat the Catalog as a queryable dataset. Good candidates: find empty msgstr, find #, fuzzy entries, find entries with no references (dead strings), find msgids duplicated across contexts. Wrap this in an Artisan command so it can run in CI.

Update in place and write back. Parse, mutate the catalog, recompile:

$parser  = new Parser(new FileSystem($path));
$catalog = $parser->parse();

$entry = $catalog->getEntry('Checkout');      // or iterate and match on msgid + msgctxt
$entry->setMsgStr('Paiement');

$po = (new PoCompiler())->compile($catalog);
file_put_contents($path . '.tmp', $po);
rename($path . '.tmp', $path);                // atomic swap

This is the pattern behind a web-based translation editor: load the catalogue, render entries in a Blade table, submit a batch of msgstr changes, mutate, recompile, write. Persisting to a database instead of the file is a common variant — store (msgctxt, msgid) as a composite key, since msgid alone is not unique once contexts are in play.

Database import for a translation memory. Flatten each entry to msgctxt | msgid | msgstr | plural forms | flags | references and upsert. Re-import on every deployment to keep the TM fresh. Always key on msgctxt + msgid, and keep plural entries as a separate row or JSON column keyed by plural index.

Merging workflows. This library does not extract strings from source code — pair it with xgettext/msgmerge (or a framework extractor) for that half of the pipeline, then use po-parser for the programmatic inspection and editing that the gettext CLI tools make awkward.

Round-trip validation in CI. Parse the file, recompile it, and diff against the original. A non-empty diff usually means someone hand-edited the file in a way the compiler normalises (line wrapping, whitespace). Fail the build on that so .po churn stays out of your pull requests.

Service wrapper. Put the parser behind a small class in app/Services exposing entries(), untranslated(), update(msgctxt, msgid, msgstr), and save(). Keeps Sepia\PoParser imports out of your controllers and gives you one place to swap in another library later.

Plural handling. Plural entries store an array of forms indexed by plural position; languages differ in how many forms they use (2 for English, 3 for Russian, 6 for Arabic). Read the header's Plural-Forms line and don't assume the count — validate that the array length matches the declared number of plurals before writing anything back.

Gotchas and Tips

Version 1 vs version 2 API. Older tutorials show $parser = new PoParser(); $parser->read($file). Current releases use new Parser(new FileSystem(...)) and parse(), and return a Catalog object rather than an array. If copy-pasted examples blow up, you are almost certainly on the other major version — check the tag in composer.lock before debugging.

Never lose the header entry. The entry with an empty msgid carries the PO file header: charset, Plural-Forms, project metadata. If you build a Catalog from scratch or filter entries carelessly, recompiling produces a file msgfmt will reject. Preserve or explicitly set the headers on any catalog you write.

"Translated" is not "msgstr is non-empty". An entry with a fuzzy flag is not trusted by gettext and must not be counted as done. Check flags alongside the string. Similarly, an entry whose msgstr equals the msgid is often an untranslated placeholder.

Charset is not converted for you. The parser gives you the bytes as written. If the header declares ISO-8859-1 but your app is UTF-8, you get mangled output downstream. Normalise the file to UTF-8 (or convert after parsing) and keep the header's Content-Type charset line consistent with what you write.

Round trips reflow text. Multi-line msgids and long msgstrs are unescaped on parse and re-escaped on compile with the compiler's own line-wrapping and ordering choices. Expect noisy diffs the first time you write a file back. Do the round-trip check in CI once, commit the normalised file, and it stops being an issue.

Writing is not atomic. FileSystem writes where you point it. If a compile fails mid-write or the process dies, you can truncate a live translation file. Always write to a temp path and rename(), and consider keeping the previous version for rollback.

Encoding of comments and references matters. Translator comments (# ...), extracted comments (#. ...), and source references (#: file.php:42) are preserved but can be dropped if you construct entries manually. If your translation UI shows "where is this string used", set the references back on the entry before compiling or that metadata is gone forever.

Whole-file parsing is in-memory. A 50k-string catalogue will parse fine but the resulting object graph is not small. Don't hold several large catalogs in memory in one request, and don't parse the same file repeatedly inside a loop — parse once, then iterate the Catalog.

No .mo generation and no string extraction. If you need compiled .mo files, run msgfmt as part of your build. If you need to find new strings, run xgettext. This library is the read/edit/write layer in the middle, not a full gettext toolchain — plan the pipeline accordingly rather than expecting it to do everything.

Extension points. If you need to parse from a database blob or a remote source, implement the source handler interface alongside FileSystem/StringSource rather than writing temp files. That is the intended seam and keeps your call sites unchanged.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings