NAME
Locale::Simple - Short gettext helpers with one API for Perl, Python, JavaScript, PHP, Go and Rust
VERSION
version 0.200
SYNOPSIS
use Locale::Simple;
l_dir('share/locale'); # dir containing <lang>/LC_MESSAGES/<domain>.mo
ltd('myapp'); # text domain
l_lang('de_DE'); # active language
print l("Hello"); # "Hallo"
print ln("You have %d message",
"You have %d messages", 4); # "Du hast 4 Nachrichten"
print lp("menu", "Open"); # context-specific lookup
Sample PO file (share/locale/de_DE/LC_MESSAGES/myapp.po):
msgid ""
msgstr ""
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;"
msgid "Hello"
msgstr "Hallo"
msgid "You have %d message"
msgid_plural "You have %d messages"
msgstr[0] "Du hast %d Nachricht"
msgstr[1] "Du hast %d Nachrichten"
Compile to .mo:
msgfmt share/locale/de_DE/LC_MESSAGES/myapp.po \
-o share/locale/de_DE/LC_MESSAGES/myapp.mo
DESCRIPTION
Locale::Simple is a thin wrapper over Locale::TextDomain / gettext, exporting short function names (l, ln, lp, ld, ldn, ldp, lnp, ldnp). The same helpers ship for Python, JavaScript, PHP, Go and Rust (see "SEE ALSO"), built from the same repository; the same msgids and the same .po files work across all six languages.
The module only supports UTF-8 data, in and out — that is fixed by design.
SETUP FUNCTIONS
l_dir($dir)
Set the locale directory. Structure underneath must follow the standard gettext layout: $dir/<lang>/LC_MESSAGES/<domain>.mo.
Every text domain reads from the directory set last: a later l_dir moves the domains already bound, whether through "ltd($domain)" or on their first use in a domain function, to the new directory.
ltd($domain)
Set (or switch) the active text domain. Binds the domain to the previously set locale directory on first use.
l_lang($lang)
Set the active language (e.g. 'de_DE', 'pt_BR'). Sets $ENV{LANGUAGE}, $ENV{LANG}, $ENV{LC_ALL}, $ENV{LC_MESSAGES} and calls setlocale.
The system does not need a locale for $lang installed. gettext translates nothing while the messages locale is C or POSIX, so when $lang cannot be set and the process runs under one of those, l_lang sets LC_MESSAGES to C.UTF-8 (or en_US.UTF-8) instead.
On a platform whose POSIX has no LC_MESSAGES category (Windows), l_lang sets the environment variables only.
l_dry($file, $nowrite)
Enable dry-run mode. While active, every translation call appends its msgid block to $file and returns the msgid (or $msgid_plural for $n != 1), formatted with the same sprintf as a translation, positional placeholders included. Useful for harvesting msgids from a running system. Pass a truthy $nowrite to suppress the file output but keep dry semantics.
The block holds the lines # domain: ..., msgctxt "...", msgid "..." and msgid_plural "..." that apply, joined by newlines and followed by a blank line, in UTF-8 with LF line ends on every platform. Backslash, newline, tab, carriage return and double quote are escaped as in a .po file. An empty msgid writes no block. The Python package writes the same bytes:
# domain: myapp
msgctxt "menu"
msgid "%d file"
msgid_plural "%d files"
Dry-run mode lifts the "l_dir($dir)" check like "l_nolocales($bool)". l_dry(undef) leaves dry-run mode and turns the check back on.
l_nolocales($bool)
Disable the sanity check that requires "l_dir($dir)" to have been called. Lets you use the module in pure sprintf mode (no gettext lookup) — handy in tests, scripts and the string-extraction pipeline.
TRANSLATION FUNCTIONS
All translation functions take sprintf-style format arguments after the required positional arguments. Perl positional-argument syntax (%1$s, %2$d) is supported and recommended for anything that needs to be reordered during translation. An empty msgid returns an empty string without a lookup.
The format takes the conversions b c d e E f F g G i o s u x X, the flags +, -, 0, space, # and ' followed by a pad character (%'*8s fills with *), a width and a .precision (either may be *), the N$ position and %%. Each placeholder is formatted on its own as sprintf formats it, so an argument value is never read as a format. A placeholder without an argument stays in the text as it is, and extra arguments are ignored, both without a warning. The Python package formats the same way.
l($msgid, @args)
Plain translation.
ln($msgid, $msgid_plural, $n, @args)
Plural-aware translation; $n selects between singular and plural forms. $n is implicitly passed as the first sprintf argument.
lp($msgctxt, $msgid, @args)
Context-aware translation. Use when the same msgid needs different translations in different UI contexts (e.g. lp("menu", "Open") vs. lp("state", "Open")).
lnp($msgctxt, $msgid, $msgid_plural, $n, @args)
Context + plural. The full gettext p_gettext_n shape.
ld($domain, $msgid, @args)
Translation from a specific domain without switching the current domain. The domain is bound to the "l_dir($dir)" directory on its first use, the way "ltd($domain)" binds it, so it needs no ltd call of its own. The three domain functions below do the same.
ldn($domain, $msgid, $msgid_plural, $n, @args)
Domain + plural.
ldp($domain, $msgctxt, $msgid, @args)
Domain + context.
ldnp($domain, $msgctxt, $msgid, $msgid_plural, $n, @args)
All four: domain, context, plural, arguments. The other functions are convenience wrappers around this one.
DEFERRED TRANSLATION
Sometimes a msgid must be declared before a user locale is known — attribute defaults, DSL registrations evaluated at use-time, class constants. Calling l() inline there freezes the result in the startup locale. Gettext's classic solution is the N_ "no-op" marker: extraction tools recognise it as a translation source, but at runtime it just returns its argument so the caller can store the msgid and translate it later.
Locale::Simple exports N_ and a twin for every l*() helper:
N_($msgid) # twin of l()
Nn_($sg, $pl) # twin of ln()
Np_($ctx, $msgid) # twin of lp()
Nnp_($ctx, $sg, $pl) # twin of lnp()
Nd_($dom, $msgid) # twin of ld()
Ndn_($dom, $sg, $pl) # twin of ldn()
Ndp_($dom, $ctx, $msgid) # twin of ldp()
Ndnp_($dom, $ctx, $sg, $pl) # twin of ldnp()
All are pure pass-throughs. Plural variants return ($sg, $pl) in list context and $sg in scalar context. The domain/context arguments contribute to the extracted .pot entry but are not returned — the caller already has them.
# Class-construction time (no locale yet):
my $label = N_($msgid); # = $msgid
my @plur = Nn_($sg, $pl); # = ($sg, $pl)
# Render time (after l_lang($user_locale)):
print l($label);
print ln(@plur, $count);
The scraper (Locale::Simple::Scraper) recognises all eight N*_ names in Perl, JavaScript, Python, PHP and Go sources and their lower-case macro twins (n_!, nn_!, ...) in Rust, and emits .pot entries identical to their l*() twins. It finds them in Xslate templates (.tx) as well, though they are rarely needed there: templates run at request time, when the language is already known.
STRING EXTRACTION
The distribution ships the locale-simple command. locale-simple scrape is a static-analysis tool that walks your source tree and writes a .pot template. It understands .pl, .pm, .t, .py, .js, .php, .go, .rs and .tx files, so one extraction run covers a polyglot project.
locale-simple scrape --ignore node_modules --ignore build \
> po/myapp.pot
See Locale::Simple::Scraper for the programmatic interface and supported options.
locale-simple po2json converts a .po file into the JSON the JavaScript package loads with loadLocaleData:
locale-simple po2json share/locale/de_DE/LC_MESSAGES/myapp.po > myapp.de_DE.json
The older commands locale_simple_scraper and po2json still work but are deprecated.
SEE ALSO
Locale::TextDomain — the underlying gettext binding.
Locale::Messages — the lower-level layer.
https://pypi.org/project/locale-simple/ — Python sibling.
https://www.npmjs.com/package/locale-simple — JavaScript sibling.
https://packagist.org/packages/raudssus/locale-simple — PHP sibling.
https://pkg.go.dev/github.com/Getty/locale-simple/go — Go sibling.
https://crates.io/crates/locale-simple — Rust sibling.
SUPPORT
Issues
Please report bugs and feature requests at https://github.com/Getty/locale-simple/issues.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.
This is free software, licensed under:
The MIT (X11) License