Revision history for Locale-Simple
0.200 2026-11-03 00:08:19Z
- New command locale-simple with the subcommands scrape and po2json
(locale-simple help lists them; --version prints the version). It exits
0 on success, 1 when a command fails and 2 for a bad call.
- Each GitHub release carries locale-simple as a standalone binary for
Linux x86_64, Linux aarch64 and macOS arm64 (Perl and modules packed
with PAR::Packer), as .tar.gz with LICENSE and README, and a SHA-256
checksums file. Linux needs glibc 2.31+ and libcrypt.so.1. scrape
writes po, perl and json everywhere; --output yaml needs the Perl
install with the YAML module.
- Scraper command: locale-simple scrape and locale_simple_scraper are the
same scraper (bin/locale_simple_scraper_old is removed);
locale_simple_scraper is deprecated and prints one notice on STDERR
naming locale-simple scrape, output and exit codes unchanged. --help (and
-h) prints the options; an unknown option, a stray argument or an
unknown --output format ends with the usage on STDERR and exit code 2,
nothing on STDOUT. --output json goes through JSON::PP with sorted keys
and UTF-8 msgids as they stand in the source; --output perl sorts the
keys too; --output yaml uses the optional YAML module (recommended in
the cpanfile).
- po2json is own code (Locale::Simple::PO2JSON, with po2data() and
data2json()) and needs only JSON::PP. It selects entries like msgfmt:
no untranslated, fuzzy or obsolete ones, any number of plural forms,
decoded multi-line strings and escapes, several quoted strings on one
line joined (msgid "a" "b" is "ab"), indented comment lines accepted.
Header values are trimmed, the header carries lang, keys come in a
fixed order. bin/po2json uses it: po2json FILE, po2json -p FILE for
indented output wrapped in the file name as domain. bin/po2json is
deprecated and prints one notice on STDERR naming the replacement,
output and exit codes unchanged: locale-simple po2json --pretty FILE
for -p; plain po2json FILE has no locale-simple equivalent.
- Scraper: recognise PHP (.php, --php), Go (.go, --go) and Rust (.rs,
--rs) sources besides Perl, Python, JavaScript and .tx templates. PHP:
heredoc, nowdoc and namespace-qualified calls. Go: the capitalised
names (L, Ln, ... Ndnp_) with or without package prefix, "..." and
backtick strings. Rust: the macros l!, ln!, ... and the markers n_!,
nn_!, ..., raw strings of any hash count. --js, --pl, --py, --tx,
--php, --go and --rs can be given more than once; the lists add up.
- Scraper: calls whose plural count or extra arguments are any code
(variables, member access, function calls, casts, brackets, strings
with commas or brackets, calls over several lines) are extracted in all
six languages, together with the translation calls inside those
arguments: l("Hello %s", $name), ln(..., items.length).
- Scraper: string literals are read the way each language reads them, so
the .pot holds the msgid the program looks up ("0" is a msgid like any
other). Python raw, unicode and triple-quoted strings are read and
adjacent literals joined; a JavaScript template literal without ${...}
is a msgid. Strings that are no constant msgid are skipped and
reported: Perl and PHP double-quoted strings and PHP heredocs that
interpolate a variable (in Perl any unescaped $, and an @ before a
name, digit, ::, ', $, +, - or {; escaped \$ and \@ stay text) and
Python f-strings and bytes.
- Scraper: files are named by their path relative to the directory the
scraper runs in (#: lines, the file field, the --md5 hash). Symlinks
that point nowhere are skipped and the files after them are read.
- Perl: l_lang() translates again in a process started under the C
locale on a system without a locale for the language (a minimal
container, a daemon without LANG): libintl-perl 1.36 and later ignore
LANGUAGE under C, so l_lang() moves LC_MESSAGES to C.UTF-8 then. On a
platform whose POSIX has no LC_MESSAGES (Windows) it sets the
environment only.
- Perl, Python: ld, ldn, ldp and ldnp bind their domain to the l_dir
directory on first use, so they need no ltd() call first; a later
l_dir moves every bound domain to the new directory.
- Perl, Python, PHP, Go, Rust: dry mode writes the same block in all
five (# domain:, msgctxt, msgid, msgid_plural) as UTF-8 with LF line
ends on every platform, with backslash, newline, tab, carriage return
and double quote escaped; the scraper's .pot escapes the same way.
JavaScript writes that block too, in one console.debug call per
translation. Python formats the result with the module's sprintf, so
%1$s placeholders work there.
- Perl, Python, JavaScript: sprintf formats each placeholder on its own
and follows the same rules: a placeholder without an argument stays as
it is, extra arguments are ignored, a % in an argument stays, %% gives
one %, ' plus a character (%'*8s) pads with that character, numbers
are read from string arguments, flags and precision work as in Perl's
sprintf. Python also takes %b; JavaScript also u, F, 0 on strings,
negative b o u x X as unsigned 64-bit, two-digit exponents and ties
rounded to the even digit.
- PHP: sprintf is the package's own and follows the same rules (a
placeholder without an argument stays, precision on integers, the
space flag, 0 with - on strings, a multibyte ' pad character, %c for
any code point, width in characters, two-digit exponents). Go, Rust:
precision on integer conversions (%.0d of 0 is empty), s and c padded
with the 0 flag, the leading number of a string argument is read; Go
keeps %0$s as it is, Rust writes -0.0 with its sign and gives U+FFFD
for a %c without a character.
- Empty msgid and "0": in Perl and JavaScript an empty msgid returns an
empty string without a lookup (JavaScript writes no dry block), and in
Perl l_dry(undef) turns the l_dir check back on; a domain, msgctxt,
msgid_plural, dry-run file or locale directory of "0" counts as set.
Python: ltd() stops with the "please set a locale directory" message
when l_dir() was not called, as l() does, and the l_dir check stops
through sys.exit, also under python -S.
- JavaScript: lookups follow the shared locale chain: l_lang('de_AT:fr')
tries de_AT, de, fr, then a bundle without language. The Plural-Forms
header is evaluated (Polish, Russian, Arabic, Japanese and others pick
the right form), each bundle's own rule applies, an empty translation
falls through to the next language and language tags are normalised
(pt-BR is pt_BR). loadLocaleData files a bundle under the language of
its gettext Language header, so bundles of several languages live side
by side in one domain; loadTranslations takes the header as optional
fourth argument.
- JavaScript: TypeScript finds the bundled types (src/index.d.ts), the
loadLocaleData types accept po2json output with its header, and
l_nolocales is exported as a no-op, like in the other five runtimes.
- PHP: new package raudssus/locale-simple for Composer. The same helpers
(l, ln, ... Ndnp_) as functions in the namespace Locale\Simple, with
its own .mo reader and Plural-Forms evaluator, so no ext-gettext is
needed. .gitattributes keeps the Perl, Python and JavaScript sources
out of git archive, so a composer require does not pull them in.
- Go: new module github.com/Getty/locale-simple (package in go/) with
the helpers as L, Ln, ... and the unchanged N_ family, an own .mo
reader and sprintf, and a Translator value for concurrent, per-request
languages.
- Rust: new crate locale-simple (rust/) with the helpers as macros l!,
ln!, ... and the lower-case markers n_!, nn_!, ..., an own .mo reader,
sprintf and Plural-Forms evaluator, and a Translator for concurrent
languages. Rust 1.67 or newer.
- Release tags are now vMAJOR.MINOR.0 (e.g. v0.200.0), valid SemVer for
all ecosystems. The npm package follows the tag and jumps from 0.1.9 to
0.200.0; npm versions now match the CPAN release. The Rust crate is
published to crates.io from the tag by CI, PHP via Packagist and Go via
the module proxy follow the same tag. The PyPI wheel and the crate
carry the MIT LICENSE file, like the npm package.
- Locale::Simple needs Perl 5.20 or newer and the cpanfile declares it,
with every test prerequisite (Test::Regression and Test::InDistDir
among them). The tests pass in the CPAN tarball and on systems that
start under the C or POSIX locale.
- Documentation: the READMEs, the POD and the usage skills describe all
six runtimes, the scraper's languages (N-markers are found in .tx
templates too) and loading po2json output in JavaScript.
0.109 2026-04-23 13:40:35Z
- Deferred-translation markers (N_, Nn_, Np_, Nd_, Ndn_, Ndp_, Nnp_,
Ndnp_) — mirror the l-family for class-construction-time use.
- Scraper: recognise the eight N*_ names (Perl / JS / Python) and emit
.pot entries identical to their l*() twins. .tx templates are not
scanned for N-markers on purpose.
- Scraper: Ldp now recognised by the parser (was listed in the arg-map
but missing from the function regex / required-args table).
0.108 2026-04-22 04:35:31Z
- Modernize build: [@Author::GETTY] bundle, cpanfile, drop weaver.ini/setup.py
- Python: fix save.push->append, make bind_textdomain_codeset optional, drop
deprecated lgettext, move to pyproject.toml
- JavaScript: rewrite as modern ESM/CJS npm package with own sprintf, drop
dead Gettext.js dependency
- POD cleanup in Locale::Simple (SUPPORT section via bundle)
0.019 2016-12-15
- Previous CPAN release (see git history for pre-modernization changes)
Keyboard Shortcuts
Global
s
Focus search bar
?
Bring up this help dialog
GitHub
gp
Go to pull requests
gi
Go to GitHub issues (only if GitHub is preferred repository)