.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .if !\nF .nr F 0 .if \nF>0 \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} .\} .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "Dist::Zilla::LocaleTextDomain 3pm" .TH Dist::Zilla::LocaleTextDomain 3pm "2018-07-08" "perl v5.26.2" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "Name" .IX Header "Name" Dist::Zilla::LocaleTextDomain \- Tools for managing Locale::TextDomain language catalogs .SH "Synopsis" .IX Header "Synopsis" In \fIdist.ini\fR: .PP .Vb 4 \& [ShareDir] \& [LocaleTextDomain] \& textdomain = My\-App \& share_dir = share .Ve .PP Scan localizable messages from your Perl libraries into a language template file, \fIpo/My\-App.pot\fR: .PP .Vb 1 \& dzil msg\-scan .Ve .PP Initialize language translation files: .PP .Vb 1 \& dzil msg\-init fr de.UTF\-8 .Ve .PP Merge changes to localizable messages into existing translation files: .PP .Vb 1 \& dzil msg\-merge .Ve .PP Compile translation files into message catalogs for testing: .PP .Vb 1 \& dzil msg\-compile \-\-dest\-dir . fr de.UTF\-8 .Ve .PP Binary message catalogs are automatically added to your distribution by the \&\f(CW\*(C`build\*(C'\fR and \f(CW\*(C`release\*(C'\fR commands: .PP .Vb 2 \& dzil build \& dzil release .Ve .SH "Description" .IX Header "Description" Locale::TextDomain provides a nice interface for localizing your Perl applications. The tools for managing translations, however, is a bit arcane. Fortunately, you can just use this plugin and get all the tools you need to scan your Perl libraries for localizable strings, create a language template, and initialize translation files and keep them up-to-date. All this is assuming that your system has the gettext utilities installed. .SH "The Details" .IX Header "The Details" I put off learning how to use Locale::TextDomain for quite a while because, while the gettext tools are great for translators, the tools for developers were a little more opaque, especially for Perlers used to Locale::Maketext. But I put in the effort while hacking Sqitch. As I had hoped, using it in my code was easy. Using it for my distribution was harder, so I decided to write Dist::Zilla::LocaleTextDomain to make life simpler for developers who manage their distributions with Dist::Zilla. .PP What follows is a quick tutorial on using Locale::TextDomain and managing its translation files with Dist::Zilla::LocaleTextDomain. .SH "This is my domain" .IX Header "This is my domain" First thing to do is to start using Locale::TextDomain in your code. Load it into each module with the name of your distribution, as set by the \f(CW\*(C`name\*(C'\fR attribute in your \fIdist.ini\fR file. For example, if your \fIdist.ini\fR looks something like this: .PP .Vb 3 \& name = My\-GreatApp \& author = Homer Simpson \& license = Perl_5 .Ve .PP Then, in you Perl libraries, load Locale::TextDomain like this: .PP .Vb 5 \& use Locale::TextDomain qw(My\-GreatApp); \& use Locale::Messages qw(bind_textdomain_filter); \& use Encode; \& $ENV{OUTPUT_CHARSET} = \*(AqUTF\-8\*(Aq; \& bind_textdomain_filter \*(AqMy\-GreatApp\*(Aq => \e&Encode::decode_utf8; .Ve .PP Locale::TextDomain uses the string we pass to it to find localization catalogs, so naturally Dist::Zilla::LocaleTextDomain will use it to put those catalogs in the right place. It's also a best practice to coerce Locale::TextDomain to return character strings, rather than bytes, by setting the \f(CW$OUTPUT_CHARSET\fR environment variable to \*(L"\s-1UTF\-8\*(R"\s0 and then binding a filter to decode the resulting strings into Perl character strings. This makes it easier to work with such strings in our application. Just be sure to encode them before outputting them! .PP Okay, so it's loaded, how do you use it? The documentation for the Locale::TextDomain exported functions is quite comprehensive, and I think you'll find it pretty simple once you get used to it. For example, simple strings are denoted with \f(CW\*(C`_\|_\*(C'\fR: .PP .Vb 1 \& say _\|_ \*(AqHello\*(Aq; .Ve .PP If you need to specify variables, use \f(CW\*(C`_\|_x\*(C'\fR: .PP .Vb 4 \& say _\|_x( \& \*(AqYou selected the color {color}\*(Aq, \& color => $color \& ); .Ve .PP Need to deal with plurals? Use \f(CW\*(C`_\|_n\*(C'\fR .PP .Vb 5 \& say _\|_n( \& \*(AqOne file has been deleted\*(Aq, \& \*(AqAll files have been deleted\*(Aq, \& $num_files, \& ); .Ve .PP And then you can mix variables with plurals with \f(CW\*(C`_\|_nx\*(C'\fR: .PP .Vb 6 \& say _\|_nx( \& \*(AqOne file has been deleted.\*(Aq, \& \*(Aq{count} files have been deleted.\*(Aq, \& $num_files, \& count => $num_files, \& ); .Ve .PP Pretty simple, right? Get to know these functions, and just make it a habit to use them in user-visible messages in your code. Even if you never expect to translate those messages, just by doing this you make it easier for someone else to come along and start translating for you. .SS "The setup" .IX Subsection "The setup" Now you've internationalized your code. Great! What's next? Officially, nothing. If you never do anything else, your code will always emit the messages as written. You can ship it and things will work just as if you had never done any localization. .PP But what's the fun in that? Let's set things up so that translation catalogs will be built and distributed once they're written. Add these lines to your \&\fIdist.ini\fR: .PP .Vb 2 \& [ShareDir] \& [LocaleTextDomain] .Ve .PP There are actually quite a few attributes you can set here to tell the plugin where to find language files and where to put them. For example, if you used a domain different from your distribution name, e.g., .PP .Vb 1 \& use Locale::TextDomain \*(Aqcom.example.My\-GreatApp\*(Aq; .Ve .PP Then you would need to set the \f(CW\*(C`textdomain\*(C'\fR attribute so that the \&\f(CW\*(C`LocaleTextDomain\*(C'\fR plugin does the right thing with the language files: .PP .Vb 2 \& [LocaleTextDomain] \& textdomain = com.example.My\-GreatApp .Ve .PP Consult the \&\f(CW\*(C`LocaleTextDomain\*(C'\fR configuration docs for details on all available attributes. .PP \&\fB(Prior to Locale::TextDomain v1.21, there was no \f(CB\*(C`ShareDir\*(C'\fB support. If you're unfortunate to be stuck with one of these earlier versions, you'll need to set the \f(CB\*(C`share_dir\*(C'\fB attribute to \f(CB\*(C`lib\*(C'\fB instead of the default value, \&\f(CB\*(C`share\*(C'\fB. If you use Module::Build, you'll also need a subclass to do the right thing with the catalog files; see \&\*(L"Installation\*(R" in Dist::Zilla::Plugin::LocaleTextDomain for details.)\fR .PP What does including the plugin do? Mostly nothing. You might see this line from \f(CW\*(C`dzil build\*(C'\fR, though: .PP .Vb 1 \& [LocaleTextDomain] Skipping language compilation: directory po does not exist .Ve .PP Now at least you know it was looking for something to compile for distribution. Let's give it something to find. .SS "Initialize languages" .IX Subsection "Initialize languages" To add translation files, use the \&\f(CW\*(C`msg\-init\*(C'\fR command: .PP .Vb 2 \& > dzil msg\-init de \& Created po/de.po. .Ve .PP At this point, the gettext utilities will need to be installed and visible in your path, or else you'll get errors. .PP This command scans all of the Perl modules gathered by Dist::Zilla and initializes a German translation file, named \fIpo/de.po\fR. This file is now ready for your German-speaking public to start translating. Check it into your source code repository so they can find it. Create as many language files as you like: .PP .Vb 4 \& > dzil msg\-init fr ja.JIS en_US.UTF\-8 \& Created po/fr.po. \& Created po/ja.po. \& Created po/en_US.po. .Ve .PP As you can see, each language results in the generation of the appropriate file in the \fIpo\fR directory, sans encoding (i.e., no \fI.UTF\-8\fR in the \f(CW\*(C`en_US\*(C'\fR file name). .PP Now let your translators go wild with all the languages they speak, as well as the regional dialects. (Don't forget to colour your code with \f(CW\*(C`en_UK\*(C'\fR translations!) .PP Once you have translations and they're committed to your repository, when you build your distribution, the language files will automatically be compiled into binary catalogs. You'll see this line output from \f(CW\*(C`dzil build\*(C'\fR: .PP .Vb 4 \& [LocaleTextDomain] Compiling language files in po \& po/fr.po: 10 translated messages, 1 fuzzy translation, 0 untranslated messages. \& po/ja.po: 10 translated messages, 1 fuzzy translation, 0 untranslated messages. \& po/en_US.po: 10 translated messages, 1 fuzzy translation, 0 untranslated messages. .Ve .PP You'll then find the catalogs in the shared directory of your distribution: .PP .Vb 4 \& > find My\-GreatApp\-0.01/share \-type f \& My\-GreatApp\-0.01/share/LocaleData/de/LC_MESSAGES/My\-GreatApp.mo \& My\-GreatApp\-0.01/share/LocaleData/en_US/LC_MESSAGES/My\-GreatApp.mo \& My\-GreatApp\-0.01/share/LocaleData/ja/LC_MESSAGES/My\-GreatApp.mo .Ve .PP These binary catalogs will be installed as part of the distribution just where \&\f(CW\*(C`Locale::TextDomain\*(C'\fR can find them. .PP Here's an optional tweak: add this line to your \f(CW\*(C`MANIFEST.SKIP\*(C'\fR: .PP .Vb 1 \& ^po/ .Ve .PP This prevents the \fIpo\fR directory and its contents from being included in the distribution. Sure, you can include them if you like, but they're not required for the running of your app; the generated binary catalog files are all you need. Might as well leave out the translation files. .SS "But I'm a Translator" .IX Subsection "But I'm a Translator" Not a developer, but want to translate a project? I've written this special section just for you. .PP Translating your language is relatively straight-forward. First, make sure that the translation file is up-to-date. Say you're translating into French; use the \f(CW\*(C`msg\-merge\*(C'\fR command to update the translation file: .PP .Vb 2 \& > dzil msg\-merge po/fr.po \& [LocaleTextDomain] Merging gettext strings into po/fr.po .Ve .PP If you get an error about it not existing, use the \&\f(CW\*(C`msg\-init\*(C'\fR command to create it: .PP .Vb 2 \& > dzil msg\-init fr \& [LocaleTextDomain] Created po/fr.po. .Ve .PP Now edit \fIpo/fr.po\fR. You can use a tool such as Poedit or Emacs to make it easier. As you work, you can use the \&\f(CW\*(C`msg\-compile\*(C'\fR command to make sure that you're translation file is error-free: .PP .Vb 2 \& > dzil msg\-compile po/fr.po \& [LocaleTextDomain] po/fr.po: 195 translated messages. .Ve .PP This command compiles a catalog file into the \fILocaleData\fR subdirectory of the current directory (or directory of your choice via the \f(CW\*(C`\-\-dest\-dir\*(C'\fR option), so that you can even run the app with the compiled catalog to make sure things look the way you think they ought to. Just set the \f(CW$LANGUAGE\fR environment variable and make sure that Perl includes the current directory in its path, something like: .PP .Vb 1 \& LANGUAGE=fr perl \-I . bin/myapp.pl .Ve .PP Consult the developer for help with this bit, as how apps run varies between projects. .SS "Mergers and acquisitions" .IX Subsection "Mergers and acquisitions" You've got translation files and helpful translators given them a workover. What happens when you change your code, add new messages, or modify existing ones? The translation files need to periodically be updated with those changes, so that your translators can deal with them. We got you covered with the \f(CW\*(C`msg\-merge\*(C'\fR command: .PP .Vb 5 \& > dzil msg\-merge \& extracting gettext strings \& Merging gettext strings into po/de.po \& Merging gettext strings into po/en_US.po \& Merging gettext strings into po/ja.po .Ve .PP This will scan your module files again and update all of the translation files with any changes. Old messages will be commented-out and new ones added. Just commit the changes to your repository and notify the translation army that they've got more work to do. .PP If for some reason you need to update only a subset of language files, you can simply list them on the command-line: .PP .Vb 3 \& > dzil msg\-merge po/de.po po/en_US.po \& Merging gettext strings into po/de.po \& Merging gettext strings into po/en_US.po .Ve .SS "What's the scan, man" .IX Subsection "What's the scan, man" Both the \f(CW\*(C`msg\-init\*(C'\fR and \f(CW\*(C`msg\-merge\*(C'\fR commands depend on a translation template file to create and merge language files. Thus far, this has been invisible: they will create a temporary template file to do their work, and then delete it when they're done. .PP However, it's common to also store the template file in your repository and to manage it directly, rather than implicitly. If you'd like to do this, the \&\f(CW\*(C`msg\-scan\*(C'\fR command will scan the Perl module files gathered by Dist::Zilla and make it for you: .PP .Vb 2 \& > dzil msg\-scan \& extracting gettext strings into po/My\-GreatApp.pot .Ve .PP The resulting \fI.pot\fR file will then be used by \f(CW\*(C`msg\-init\*(C'\fR and \f(CW\*(C`msg\-merge\*(C'\fR rather than scanning your code all over again. This actually then makes \f(CW\*(C`msg\-merge\*(C'\fR a two-step process: You need to update the template before merging. Updating the template is done by exactly the same command, \f(CW\*(C`msg\-scan\*(C'\fR: .PP .Vb 6 \& > dzil msg\-scan \& extracting gettext strings into po/My\-GreatApp.pot \& > dzil msg\-merge \& Merging gettext strings into po/de.po \& Merging gettext strings into po/en_US.po \& Merging gettext strings into po/ja.po .Ve .SS "Ship It!" .IX Subsection "Ship It!" And that's all there is to it. Go forth and localize and internationalize your Perl apps! .SH "Author" .IX Header "Author" David E. Wheeler .SH "Contributor" .IX Header "Contributor" Charles McGarvey .SH "Copyright and License" .IX Header "Copyright and License" This software is copyright (c) 2012\-2017 by David E. Wheeler. .PP This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.