NAME
Locale::Simple - Functions for translate text based on gettext data, also in JavaScript
VERSION
version 0.109
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) that are API-compatible with the matching Python package locale-simple and the npm package locale-simple. The same msgids and the same .po files work across all three 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.
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.
l_dry($file, $nowrite)
Enable dry-run mode. While active, every translation call appends its msgid in .po format to $file and still returns the formatted result. Useful for harvesting msgids from a running system. Pass a truthy $nowrite to suppress the file output but keep dry semantics.
l_nolocales($bool)
Disable the sanity check that requires "l_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.
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.
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 across .pl/.pm/.t/.js/.py and emits .pot entries identical to their l*() twins. Templates (.tx) are not scanned for N-markers — they run at request time, so the deferred pattern serves no purpose there.
STRING EXTRACTION
The distribution ships bin/locale_simple_scraper, a static-analysis tool that walks your source tree and writes a .pot template. It understands .pl, .pm, .t, .py, .js and .tx files uniformly, so one extraction run covers a polyglot project.
locale_simple_scraper --ignore node_modules --ignore build \
> po/myapp.pot
See Locale::Simple::Scraper for the programmatic interface and supported options.
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.
SUPPORT
Issues
Please report bugs and feature requests on GitHub 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