Locale::TextDomain(3)

๐Ÿฆ‹ Locale::TextDomain(3) โ€” Perl Interface to Uniforum Message Translation ๐Ÿฆ‹

Okay bestie, this is the Locale::TextDomain(3) page from User Contributed Perl Documentation. It's giving high-level Perl message translation, and honestly it ate. ๐Ÿ’…


โœจ SYNOPSIS โœจ

Here's the whole mood in code, no cap:

use Locale::TextDomain ('my-package', @locale_dirs);

use Locale::TextDomain qw (my-package);

my $translated = __"Hello World!\n";

my $alt = $__{"Hello World!\n"};

my $alt2 = $__->{"Hello World!\n"};

my @list = (N__"Hello",
            N__"World");

printf (__n ("one file read",
             "%d files read",
             $num_files),
        $num_files);

print __nx ("one file read", "{num} files read", $num_files,
            num => $num_files);

my $translated_context = __p ("Verb, to view", "View");

printf (__np ("Files read from filesystems",
              "one file read",
              "%d files read",
              $num_files),
        $num_files);

print __npx ("Files read from filesystems",
             "one file read",
             "{num} files read",
             $num_files,
             num => $num_files);

๐Ÿ’– DESCRIPTION ๐Ÿ’–

So Locale::TextDomain(3pm) is the module that gives you a high-level interface to Perl message translation. Iconic behavior, truly.

๐Ÿซถ Textdomains

When you request a translation for a given string, the system used in libintl-perl follows a standard strategy to find a suitable message catalog containing the translation: unless you explicitly define a name for the message catalog, libintl-perl will assume that your catalog is called 'messages' (unless you have changed the default value to something else via Locale::Messages(3pm), method textdomain()).

You might think that this default strategy leaves room for optimization, and you are right. It would be a lot smarter if multiple software packages, all with their individual message catalogs, could be installed on one system, and it should also be possible that third-party components of your software (like Perl modules) can load their message catalogs, too, without interfering with yours.

The solution is clear: you have to assign a unique name to your message database, and you have to specify that name at run-time. That unique name is the so-called textdomain of your software package. The name is actually arbitrary, but you should follow these best-practice guidelines to ensure maximum interoperability:

  • File System Safety โ€” In practice, textdomains get mapped into file names, and you should therefore make sure that the textdomain you choose is a valid filename on every system that will run your software.
  • Case-sensitivity โ€” Textdomains are always case-sensitive (i.e. 'Package' and 'PACKAGE' are not the same). However, since the message catalogs will be stored on file systems, that may or may not distinguish case when looking up file names, you should avoid potential conflicts here.
  • Textdomain Should Match CPAN Name โ€” If your software is listed as a module on CPAN, you should simply choose the name on CPAN as your textdomain. The textdomain for libintl-perl is hence 'libintl-perl'. But please replace all periods ('.') in your package name with an underscore because ...
  • Internet Domain Names as a Fallback โ€” ... if your software is not a module listed on CPAN, as a last resort you should use the Java(tm) package scheme, i.e. choose an internet domain that you are owner of (or ask the owner of an internet domain) and concatenate your preferred textdomain with the reversed internet domain. Example: your company runs the web-site 'www.foobar.org' and is the owner of the domain 'foobar.org'. The textdomain for your company's software 'barfoos' should hence be 'org.foobar.barfoos'.

If your software is likely to be installed in different versions on the same system, it is probably a good idea to append some version information to your textdomain.

Other systems are less strict with the naming scheme for textdomains, but the phenomena known as Perl is actually a plethora of small, specialized modules, and it is probably wisest to postulate some namespace model in order to avoid chaos.

๐Ÿฆ‹ Binding textdomains to directories

Once the system knows the textdomain of the message that you want to get translated into the user's language, it still has to find the correct message catalog. By default, libintl-perl will look up the string in the translation database found in the directories /usr/share/locale and /usr/local/share/locale (in that order).

It is neither guaranteed that these directories exist on the target machine, nor can you be sure that the installation routine has write access to these locations. You can therefore instruct libintl-perl to search other directories prior to the default directories. Specifying a different search directory is called binding a textdomain to a directory.

Beginning with version 1.20, Locale::TextDomain extends the default strategy by a Perl-specific approach. If File::ShareDir is installed, it will look in the subdirectories named locale and LocaleData (in that order) in the directory returned by File::ShareDir::dist_dir ($textdomain) (if File::ShareDir is installed), and check for a database containing the message for your textdomain there. This allows you to install your database in the Perl-specific shared directory using Module::Install's install_share directive or the Dist::Zilla ShareDir plugin.

If File::ShareDir is not available, or if Locale::TextDomain fails to find the translation files in the File::ShareDir directory, it will next look in every directory found in the standard include path @INC, and check for a database containing the message for your textdomain there. Example: if the path /usr/lib/perl/5.8.0/site_perl is in your @INC, you can install your translation files in /usr/lib/perl/5.8.0/site_perl/LocaleData, and they will be found at run-time.


๐Ÿ’… USAGE ๐Ÿ’…

It is crucial to remember that you use Locale::TextDomain(3) as specified in the section "SYNOPSIS" โ€” that means you have to use it, not require it. The module behaves quite differently compared to other modules. Vibe check passed only if you use it right.

The most significant difference is the meaning of the list passed as an argument to the use() function. It actually works like this:

use Locale::TextDomain (TEXTDOMAIN, DIRECTORY, ...)

The first argument (the first string passed to use()) is the textdomain of your package, optionally followed by a list of directories to search instead of the Perl-specific directories (see above: /LocaleData appended to a File::ShareDir directory and every path in @INC).

If you are the author of a package 'barfoos', you will probably put the line

use Locale::TextDomain 'barfoos';

resp. for non-CPAN modules

use Locale::TextDomain 'org.foobar.barfoos';

in every module of your package that contains translatable strings. If your module has been installed properly, including the message catalogs, it will then be able to retrieve these translations at run-time.

If you have not installed the translation database in a directory LocaleData in the File::ShareDir directory or the standard include path @INC (or in the system directories /usr/share/locale resp. /usr/local/share/locale), you have to explicitly specify a search path by giving the names of directories (as strings!) as additional arguments to use():

use Locale::TextDomain qw (barfoos ./dir1 ./dir2);

Alternatively you can call the function bindtextdomain() with suitable arguments (see the entry for bindtextdomain() in "FUNCTIONS" in Locale::Messages). If you do so, you should pass undef as an additional argument in order to avoid unnecessary lookups:

use Locale::TextDomain ('barfoos', undef);

You see that the arguments given to use() have nothing to do with what is imported into your namespace, but they are rather arguments to textdomain(), resp. bindtextdomain(). Does that mean that Locale::TextDomain exports nothing into your namespace? Umh, not exactly ... in fact it imports all functions listed below into your namespace, and hence you should not define conflicting functions (and variables) yourself.

So, why has Locale::TextDomain to be different from other modules? If you have ever written software in C and prepared it for internationalization (i18n), you will probably have defined some preprocessor macros like:

#define _(String) dgettext ("my-textdomain", String)
#define N_(String) String

You only have to define that once in C, and the textdomain for your package is automatically inserted into all gettext functions. In Perl there is no such mechanism (at least it is not portable, option -P) and using the gettext functions could become quite cumbersome without some extra fiddling:

print dgettext ("my-textdomain", "Hello world!\n");

This is no fun. In C it would merely be a

printf (_("Hello world!\n"));

Perl has to be more concise and shorter than C ... see the next section for how you can use Locale::TextDomain to end up in Perl with a mere

print __"Hello World!\n";

๐Ÿ”ฅ EXPORTED FUNCTIONS ๐Ÿ”ฅ

All functions have quite funny names on purpose. In fact the purpose for that is quite clear: they should be short, operator-like, and they should not yell for conflicts with existing functions in your namespace. You will understand it when you internationalize your first Perl program or module. Preparing it is more like marking strings as being translatable than inserting function calls. Here we go:

๐ŸŒŸ __ MSGID ๐ŸŒŸ

NOTE: This is a double underscore!

The basic and most-used function. It is a short-cut for a call to gettext() resp. dgettext(), and simply returns the translation for MSGID. If your old code reads like this:

print "permission denied";

You will now write:

print __"permission denied";

That's all โ€” the string will be output in the user's preferred language, provided that you have installed a translation for it.

Of course you can also use parentheses:

print __("permission denied");

Or even:

print (__("permission denied"));

In my eyes, the first version without parentheses looks best.

๐ŸŒŸ __x MSGID, ID1 => VAL1, ID2 => VAL2, ... ๐ŸŒŸ

One of the nicest features in Perl is its capability to interpolate variables into strings:

print "This is the $color $thing.\n";

This nice feature might con you into thinking that you could now write

print __"This is the $color $thing.\n";

Ok so back to that thing we were doing โ€” and bestie, alas, that would be nice, but it is not possible. ๐Ÿ˜ญ Let me explain why, because this is like, the one gotcha that gets everybody.

Remember that __() is doing double duty โ€” it's both an operator for translating strings and a little flag that marks strings as translatable. So if that string we were looking at got extracted out of your Perl code, what lands in the message catalog is the un-interpolated form, because when the parser is reading your code it cannot possibly predict what values $thing and $color are gonna have at run-time (and honestly that unpredictability is probably one of the reasons you wrote the program in the first place ๐Ÿ’€).

But at run-time? Perl has already interpolated those values before __() (or the gettext() underneath it) ever lays eyes on the original string. So something like "This is the red car.\n" gets looked up in the message catalog, it does not get found (because the database only has "This is the $color $thing.\n" in it), and you just get the original untranslated string back. ๐Ÿ˜ฌ And since this is basically always an error, xgettext(1) will straight-up bail out with a fatal error when it comes across that string in your code. Iconic of it, honestly.

There are two workarounds for that:

printf __"This is the %s %s.\n", $color, $thing;

But nnn, that has several disadvantages: your translator only sees the isolated string, and without the surrounding code it's almost impossible to interpret correctly. Sure, GNU emacs and other software capable of editing PO translation files will let you peek at the context in the source code, but likeโ€ฆ it's way more likely your translator will go find a less challenging translation project when she keeps running into messages like this. ๐Ÿซ 

And even if she does understand the underlying programming โ€” what if she has to reorder the color and the thing, like in French:

msgid "This is the red car.\n";
msgstr "Cela est la voiture rouge.\n"

Zut alors! โœจ Yeah, you can reorder the arguments to printf() and friends, but it requires a syntax that is nothing that you want to learn. ๐Ÿ™ˆ

So what's the move? The Perl backend to GNU gettext has defined an alternative format for interpolatable strings:

"This is the {color} {thing}.\n";

Instead of Perl variables you use place-holders in curly braces (and legal Perl variables are also legal place-holders, iykyk), and then you call

print __x ("This is the {color} {thing}.\n",
           thing => $thang,
           color => $color);

The function __x() takes that additional hash and replaces all occurrences of the hash keys in curly braces with the corresponding values. Simple, readable, understandable to translators โ€” what else would you want? ๐ŸŽ€ And if the translator forgets, misspells, or otherwise messes up some "variables", msgfmt(1) โ€” the program used to compile the textual translation file into its binary representation โ€” will actually choke on those errors and refuse to compile the translation. Vibe check: failed. Translation: not shipping. ๐Ÿ’…


__n MSGID, MSGID_PLURAL, COUNT

Whew! ๐Ÿ˜ฎโ€๐Ÿ’จ That looks complicatedโ€ฆ and the best way to get it is with an example. Let's have another look at your vintage code:

if ($files_deleted > 1) {
    print "All files have been deleted.\n";
} else {
    print "One file has been deleted.\n";
}

Your intent is clear โ€” you wanted to dodge the cumbersome "1 files deleted". And for English that's fine, but other languages have more than one plural form. Like in Russian it makes a difference whether you want to say 1 file, 3 files, or 6 files โ€” you use three different forms of the noun 'file' in each case. [Note: yep, very smart you are, the Russian word for 'file' is in fact the English word, and it's an invariable noun โ€” but if you know that, you'll also understand the rest despite this little simplificationโ€ฆ ๐Ÿ˜Œ]

That's literally the reason ngettext() exists, and __n() is a short-cut for it:

print __n"One file has been deleted.\n",
         "All files have been deleted.\n",
         $files_deleted;

Alternatively:

print __n ("One file has been deleted.\n",
           "All files have been deleted.\n",
           $files_deleted);

The effect is always the same: libintl-perl figures out which plural form to pick for your user's language, and the output string always looks okay. ๐Ÿ‘‘

__nx MSGID, MSGID_PLURAL, COUNT, VAR1 => VAL1, VAR2 => VAL2, ...

Bringing it all together, bestie:

print __nx ("One file has been deleted.\n",
            "{count} files have been deleted.\n",
            $num_files,
            count => $num_files);

The function __nx() picks the correct plural form (also for English!) and it's capable of interpolating variables into strings.

Now look closely at the order of arguments, because this is where people trip: the first argument is the string in the singular, the second one is the plural string, and the third one is an integer indicating the number of items. That third argument is only used to pick the correct translation. The optionally following arguments make up the hash used for interpolation. And yeah โ€” in the beginning it's often a little confusing that the variable holding the number of items will usually be repeated somewhere in the interpolation hash. ๐ŸŒ€ That's normal. You're doing great.

__xn MSGID, MSGID_PLURAL, COUNT, VAR1 => VAL1, VAR2 => VAL2, ...

Does exactly the same thing as __nx(). In fact it's a common typo that got promoted to a feature. We love a glow-up. โœจ

__p MSGCTXT, MSGID

This one's much like __. The "p" stands for "particular", and the MSGCTXT is there to give the translator some context. That's necessary when your string is short and could stand for multiple things. For example:

print __p"Verb, to view", "View";
print __p"Noun, a view", "View";

The above could be "View" entries in a menu, where View->Source and File->View are different forms of "View" and likely need to be translated differently. ๐Ÿ“

A typical usage is GUI programs. Imagine a program with a main menu and the notorious "Open" entry in the "File" menu. Now imagine there's another menu entry, Preferences->Advanced->Policy, where you have a choice between the alternatives "Open" and "Closed". In English, "Open" is the adequate text at both places. In other languages, it is very likely you need two different translations. So you'd now write:

__p"File|", "Open";
__p"Preferences|Advanced|Policy", "Open";

In English โ€” or if no translation can be found โ€” the second argument (MSGID) is returned.

This function was introduced in libintl-perl 1.17.

__px MSGCTXT, MSGID, VAR1 => VAL1, VAR2 => VAL2, ...

Like __p(), but it supports variable substitution in the string, like __x().

print __px("Verb, to view", "View {file}", file => $filename);

See __p() and __x() for more details.

This function was introduced in libintl-perl 1.17.

__np MSGCTXT, MSGID, MSGID_PLURAL, COUNT

This adds context to plural calls. It should not be needed very often, if at all, thanks to the __nx() function. The type of variable substitution used in other gettext libraries (using sprintf-like symbols, like %s or %1) sometimes required context. For a (bad) example of this:

printf (__np("[count] files have been deleted",
             "One file has been deleted.\n",
             "%s files have been deleted.\n",
             $num_files),
        $num_files);
NOTE: the above usage is discouraged. Just use the __nx() call, which provides inline context via the key names. ๐Ÿ™

This function was introduced in libintl-perl 1.17.

__npx MSGCTXT, MSGID, MSGID_PLURAL, COUNT, VAR1 => VAL1, VAR2 => VAL2, ...

This is provided for completeness. It adds the variable interpolation into the string to the previous method, __np().

Its usage would be like so:

print __npx ("Files being permenantly removed",
             "One file has been deleted.\n",
             "{count} files have been deleted.\n",
             $num_files,
             count => $num_files);

I cannot think of any situations requiring this, but we can easily support it, so here it is. ๐Ÿคทโ€โ™€๏ธ

This function was introduced in libintl-perl 1.17.

N__(ARG1)

A no-op function that simply echoes its arguments back to the caller. Take the following piece of Perl:

my @options = (
    "Open",
    "Save",
    "Save As",
);

...

my $option = $options[1];

Now say you want to have this translatable. Sometimes you could just do:

my @options = (
    __"Open",
    __"Save",
    __"Save As",
);

...

my $option = $options[1];

But often times this will not be what you want โ€” for example, when you also need the unmodified original string. And sometimes it may not even work, like when the preferred user language isn't determined yet at the time the list gets initialized. ๐Ÿ˜ตโ€๐Ÿ’ซ

In those cases you'd write:

my @options = (
    N__"Open",
    N__"Save",
    N__"Save As",
);

...

my $option = __($options[1]);
# or: my $option = dgettext ('my-domain', $options[1]);

Now all the strings in @options are left alone, since N__() returns its arguments (one or more) unmodified. Nevertheless, the string extractor will still be able to recognize the strings as being translatable. And you can still get the translation later by passing the variable instead of the string to one of the above translation functions. Best of both worlds, no cap. ๐Ÿ’š

N__n (MSGID, MSGID_PLURAL, COUNT)

Does exactly the same as N__(). You'll use this form if you have to mark the strings as having plural forms.

N__p (MSGCTXT, MSGID)

Marks MSGID as N__() does, but in the context MSGCTXT.

N__np (MSGCTXT, MSGID, MSGID_PLURAL, COUNT)

Marks MSGID as N__n() does, but in the context MSGCTXT. ๐Ÿฆ‹

๐Ÿ’– EXPORTED VARIABLES ๐Ÿ’–

okay so bestie, the module exports several variables into your namespace, and they're lowkey main character energy:

๐ŸŽ€ %__ ๐ŸŽ€

A tied hash. Its keys are your original messages, the values are their translations:

my $title = "<h1>$__{'My Homepage'}</h1>";

This is much better for your translation team than

my $title = __"<h1>My Homepage</h1>";

In the second case the HTML code will make it into the translation database and your translators have to be aware of HTML syntax when translating strings. not ideal, fr fr ๐Ÿ˜ญ

โš ๏ธ Warning: Do not use this hash outside of double-quoted strings! The code in the tied hash object relies on the correct working of the function caller() (see perldoc -f caller), and this function will report incorrect results if the tied hash value is the argument to a function from another package, for example:
my $result = Other::Package::do_it ($__{'Some string'});

The tied hash code will see "Other::Package" as the calling package, instead of your own package. Consequently it will look up the message in the wrong text domain. There is no workaround for this bug. Therefore:

Never use the tied hash interpolated strings!

โœจ $__ โœจ

A reference to %__, in case you prefer:

my $title = "<h1>$__->{'My Homepage'}</h1>";

๐ŸŽ€ CLASS METHODS ๐ŸŽ€

The following class methods are defined:

๐Ÿ’… options

Returns a space-separated list of all --keyword and all --flag options for xgettext(1), when extracting strings from Perl source files localized with Locale::TextDomain.

The option should rather be called xgettextDefaultOptions. With regard to the typical use-case, a shorter name has been picked:

xgettext `perl -MLocale::TextDomain -e 'print Locale::TextDomain->options'`

See https://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html for more information about the xgettext options --keyword and --flag.

If you want to disable the use of the xgettext default keywords, you should pass an option --keyword="" to xgettext before the options returned by this method.

If you doubt the usefulness of this method, check the output on the command-line:

perl -MLocale::TextDomain -e 'print Locale::TextDomain->options'

Nothing that you want to type yourself. ๐Ÿ˜ญ

This method was added in libintl-perl 1.28.

โญ keywords

Returns a space-separated list of all --keyword options for xgettext(1) so that all translatable strings are properly extracted.

This method was added in libintl-perl 1.28.

๐ŸŒˆ flags

Returns a space-separated list of all --flag options for xgettext(1) so that extracted strings are properly flagged.

This method was added in libintl-perl 1.28.

๐Ÿ”ฅ PERFORMANCE ๐Ÿ”ฅ

Message translation can be a time-consuming task. Take this little example:

1: use Locale::TextDomain ('my-domain');
2: use POSIX (:locale_h);
3:
4: setlocale (LC_ALL, '');
5: print __"Hello world!\n";

This will usually be quite fast, but in pathological cases it may run for several seconds. A worst-case scenario would be a Chinese user at a terminal that understands the codeset Big5-HKSCS. Your translator for Chinese has however chosen to encode the translations in the codeset EUC-TW.

What will happen at run-time? First, the library will search and load a (maybe large) message catalog for your textdomain my-domain. Then it will look up the translation for Hello world!\n, it will find that it is encoded in EUC-TW. Since that differs from the output codeset Big5-HKSCS, it will first load a conversion table containing several ten-thousands of codepoints for EUC-TW, then it does the same with the smaller, but still very large conversion table for Big5-HKSCS, it will convert the translation on the fly from EUC-TW into Big5-HKSCS, and finally it will return the converted translation.

A worst-case scenario but realistic. And for these five lines of code, there is not much you can do to make it any faster. You should understand, however, when the different steps will take place, so that you can arrange your code for it.

You have learned in the section DESCRIPTION that line 1 is responsible for locating your message database. However, the use() will do nothing more than remembering your settings. It will not search any directories, it will not load any catalogs or conversion tables.

Somewhere in your code you will always have a call to POSIX::setlocale(), and the performance of this call may be time-consuming, depending on the architecture of your system. On some systems, this will consume very little time, on others it will only consume a considerable amount of time for the first call, and on others it may always be time-consuming. Since you cannot know, how setlocale() is implemented on the target system, you should reduce the calls to setlocale() to a minimum.

Line 5 requests the translation for your string. Only now, the library will actually load the message catalog, and only now will it load eventually needed conversion tables. And from now on, all this information will be cached in memory. This strategy is used throughout libintl-perl, and you may describe it as load-on-first-access. Getting the next translation will consume very little resources.

However, although the translation retrieval is somewhat obfuscated by an operator-like function call, it is still a function call, and in fact it even involves a chain of function calls. Consequently, the following example is probably bad practice:

foreach (1 .. 100_000) {
    print __"Hello world!\n";
}

This example introduces a lot of overhead into your program. Better do this:

my $string = __"Hello world!\n";
foreach (1 .. 100_000) {
    print $string;
}

The translation will never change, there is no need to retrieve it over and over again. Although libintl-perl will of course cache the translation read from the file system, you can still avoid the overhead for the function calls.

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Copyright (C) 2002-2017 Guido Flohr <http://www.guido-flohr.net/> (<mailto:guido.flohr@cantanea.com>), all rights reserved. See the source code for details!code for details!

๐ŸŒˆ SEE ALSO ๐ŸŒˆ

mwah ๐Ÿ’‹ โ€” this iconic little page is from perl v5.42.0, dated 2025-01-22, and it's Locale::TextDomain(3). go be the main character of your translation workflow ๐Ÿซถโœจ