{"id":18062233,"url":"https://github.com/gflohr/template-plugin-gettext","last_synced_at":"2025-04-11T15:04:32.355Z","repository":{"id":56834575,"uuid":"68141038","full_name":"gflohr/Template-Plugin-Gettext","owner":"gflohr","description":"Localization for the Template Toolkit 2","archived":false,"fork":false,"pushed_at":"2023-06-16T06:24:02.000Z","size":164,"stargazers_count":4,"open_issues_count":1,"forks_count":5,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T11:11:23.537Z","etag":null,"topics":["gettext","i18n","internationalization","l10n","localization","po-files","template-toolkit","xgettext"],"latest_commit_sha":null,"homepage":"http://www.guido-flohr.net/en/projects/#Template-Plugin-Gettext","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gflohr.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-09-13T19:38:05.000Z","updated_at":"2023-03-15T09:59:11.000Z","dependencies_parsed_at":"2024-06-19T04:12:22.541Z","dependency_job_id":null,"html_url":"https://github.com/gflohr/Template-Plugin-Gettext","commit_stats":{"total_commits":140,"total_committers":3,"mean_commits":"46.666666666666664","dds":"0.021428571428571463","last_synced_commit":"c557209af6843e1e69cb06b303a81204ab4a9722"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gflohr%2FTemplate-Plugin-Gettext","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gflohr%2FTemplate-Plugin-Gettext/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gflohr%2FTemplate-Plugin-Gettext/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gflohr%2FTemplate-Plugin-Gettext/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gflohr","download_url":"https://codeload.github.com/gflohr/Template-Plugin-Gettext/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248429062,"owners_count":21101780,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gettext","i18n","internationalization","l10n","localization","po-files","template-toolkit","xgettext"],"created_at":"2024-10-31T05:06:20.820Z","updated_at":"2025-04-11T15:04:32.329Z","avatar_url":"https://github.com/gflohr.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Template-Plugin-Gettext\n\nLocalization for the Template Toolkit 2\n\nThe POD version of this document exhibits errors.  Consider reading the markdown version instead at https://github.com/gflohr/Template-Plugin-Gettext.\n\n## Description\n\nThis Perl library offers an end-to-end localization and internationalization solution for the Template Toolkit 2.\nIt consists of a plugin that offers translation functions inside templates\nand a string extractor `xgettext-tt2` that extracts translatable strings\nfrom templates and writes them to PO files (or rather a `.pot` file in PO\nformat).  The string extractor `xgettext-tt2` is fully\ncustomizable and also usable for other i18n plugins or\nframeworks for the Template Toolkit.\n\n## Usage\n\nThe solution offered by this library is suitable for templates that have \na lot of markup (normally HTML) compared to text.  If the files contain\na lot of content other solutions are probably more suitable.  One of them\nis [xml2po](https://github.com/mate-desktop/mate-doc-utils/tree/master/xml2po),\nespecially if the input format is HTML.\n\nIf the input format is Markdown, for example for a static side generator,\na feasible approach may be to simply split the input into paragraphs, and\nturn each paragraph into an entry of a PO file.\n\nIn the following, we will assume that you have decided to localize\ntemplates with this library.\n\n### Templates\n\nThe first step is to mark all translatable strings.  This serves\na double purpose.  Strings are marked, so that the extractor \n`xgettext-tt2` can find them and write them into a translation file \nin PO format.\n\nThe second purpose is that these markers are also valid functions\nresp. filters for the template toolkit and will interpolate the\ntranslations for these messages into the output, when rendering the\ntemplate.  As a result, your templates remain pretty readable after\nlocalizing them.\n\nIn every source file that you want to use translations, you have\nto `USE` the template:\n\n    [% USE gtx = Gettext('com.mydomain.www', 'fr') %]\n\nDo *not* forget to `USE` the plug-in in all templates!  The template\ntoolkit will not warn you, when you forget it but the translation \nmechanism will not work!\n\nThe first argument is the so-called *textdomain*.  This is the\nidentifier for your message catalogs and also the basename of several\nfiles.  In the example above, the translated message catalog would\nbe searched as *`LOCALEDIR`*`/fr/LC_MESSAGES/com.mydomain.www.mo`. The second parameter is the language.  This will normally come from\na variable instead of a hard-coded string.\n\nA possible third argument (omitted in the example) is the character\nset to use, all following arguments are additional directories to\nsearch first for translations.\n\nThe default list of directories is:\n\n* `./locale`\n* `/usr/share/locale`\n* `/usr/local/share/locale`\n\nThe directory `./locale` is relative to the current working directory\nfrom where you invoke the template processor.\n\n#### Simple Translations With `gettext()`\n\nThe simplest and most common way of doing things is:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n\u003ctitle\u003e[% gtx.gettext(\"World Of Themes\") %]\u003c/title\u003e\n    \n\u003ch1\u003e[% \"Introduction\" | gettext %]\n\n\u003cp\u003e\n[% FILTER gettext %]\nThe \"World Of Themes\" is the ultimate source of templates\nfor the Template Toolkit.\n[% END %]\n\u003c/p\u003e\n```\n\nThis shows three different ways of localizing strings.  You can\nuse the function `gtx.gettext()`, the filter `gettext` with pipe\nsyntax, or the same filter with block syntax.  The result is always\nthe same.  The string will be recognized as translatable by \n`xgettext-tt2` and it will be translated into the selected language,\nwhen rendering the template.\n\n#### Interpolating Strings Into Translations\n\nOne important thing to understand is that the argument to the\ngettext functions or filters is the lookup key into the translation\ndatabase, when the template gets rendered.  That implies that this\nkey has to be invariable and must not use any interpolated variables.\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n[% gtx.gettext(\"Hello, $firstname $lastname!\") %]\n```\n\nThis template code is syntactically correct and will also render\ncorrectly.  But `xgettext-tt2` will bail out on it with an error\nmessage like\n\n    templates.html:3: Illegal variable interpolation at \"$\"\n\nThe function `gettext()` will receive the interpolated string\nas its argument, and that is not the same as the string that\nthe extractor program `xgettext-tt2` sees.  And that means that\nthe translation cannot be found.\n\nThe correct way to interpolate strings uses `xgettext()`:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n[% gtx.xgettext(\"Hello, {first} {last}!\",\n                first =\u003e firstname, last =\u003e lastname) %]\n[% \"Hello, {first} {last}!\" | xgettext(first =\u003e firstname, \n                                       last =\u003e lastname) %]\n[% FILTER xgettext(first =\u003e firstname, last =\u003e lastname) %]\nHello, {first} {last}!\n[% END %]\n```\n\nOne additional benefit of this is that the extractor program\n`xgettext-tt2` will also mark these strings with the flag\n\"perl-brace-format\".  When the translation from the `.po`\nfile gets compiled into an `.mo` file, the compiler `msgfmt`\nchecks that the translated strings contains exactly the same\nplaceholders as the original.\n\nOne thing that you should also avoid is to assemble strings\nin the template source code.  Do *not*:\n\n```html\n[% gtx.gettext(\"Please contact\") %] [% name %]\n[% gtx.gettext(\"for help about the\") %] [% package %]\n[% gtx.gettext(\"software.\") %]\n```\n\nThis will result in three translatable text snippets\n\"Please contact\", \"for help about the\", and \"software.\" that\nare hard to translate without context.  Besides it makes\nillegal assumptions about the word order in translated sentences.\nInstead, use `xgettext()` and write in complete sentences with\nplaceholders.\n\nBy the way, the `x` in the function `xgettext()` stands for *eXpand*\nwhile the `x` in the program `xgettext-tt2` or GNU Gettext's\n`xgettext` program stands for *eXtract*.\n\n#### Plural Forms\n\nDo *not* write this:\n\n```html\n[% IF num != 1 %]\n[% gtx.xgettext(\"{number} documents deleted!\", number =\u003e num) %]\n[% ELSE %]\n[% gtx.gettext(\"One document deleted!\") %]\n[% END %]\n```\n\nThis assumes that every language has one singular and one plural\n(and no other forms) and that the condition that selects the correct\nform is always `COUNT != 1`.  But this is wrong for many languages\nfor example Russian (two plural forms), Chinese (no plural), French\n(different condition), and many more.\n\nWrite instead:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n[% gtx.nxgettext(\"One document deleted.\", \n                 \"{count} documents deleted.\"\n                 num,\n                 count =\u003e num) %]\n```\n\nThe function `nxgettext()` receives the singular and plural\nform as the first and second argument, followed by the number\nof items, followed by an arbitrary number of key/value pairs\nfor interpolating variables in the strings.\n\nThere is also a function `ngettext()` that does not expand\nits first two arguments.  You will find out that you almost\nnever need that function.\n\nYou can also use `nxgettext()` and `ngettext()` as filters.\nBut the necessary code is awkward, and their use is therefore\nnot recommended.\n\n#### Ambiguous Strings (message contexts)\n\nSometimes an English string has different meanings in other\nlanguages:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n[% gtx.gettext(\"State:\") %]\n[% IF state == '1' %]\n[% gtx.pgettext(\"state\", \"Open\") %]\n[% ELSE %]\n[% gtx.gettext(\"Closed\") %]\n[% END %]\n\u003ca href=\"/action/open\"\u003e[% gtx.pgettext(\"action\", \"Open\") %]\u003c/a\u003e\n```\n\nThe function `pgettext()` works like gettext but has one \nextra argument preceding the string, the so-called\nmessage context.  The string extractor `xgettext-tt2` will now\ncreate two distinct messages \"Open\", one with the context \"state\",\nthe other one with the context \"action\".  The sole purpose of this\ncontext is to disambiguate the string \"Open\" for languages where the\nverb (\"to open\") and the adjective (\"the door is *open*\") has\ntwo distinct translations.\n\nYou will normally use this function, when a translator asks you\nto do so, but not on your own behalf.\n\nThere is also a function `pxgettext()` that supports placeholder\ninterpolation, and `npxgettext()` that has the following semantics:\n\n```perl\nnpxgettext(CONTEXT, SINGULAR, PLURAL, COUNT,\n           KEY1 =\u003e VALUE1, KEY2 =\u003e VALUE2, ...)\n```\n\n#### More Esoteric Functions\n\nThe [API documentation](lib/Template/Plugin/Gettext.pod#user-content-FUNCTIONS) contains\nsome more functions and filters that are available for completeness.\nYou will never need them in normal projects.\n\n#### Translator Hints\n\nYou can add comments to the source code that are copied into the\n`.po` file as hints for the translators.  This will look like\nthis:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n\u003c!-- TRANSLATORS: This is the day of the week! --\u003e\n[% gtx.gettext(\"Sun\") %]\n```\n\nIn order to make that work, you have to invoke the extractor\nprogram `xgettext-tt2` like this:\n\n    xgettext-tt2 --add-comments=TRANSLATORS: t1.html t2.html ...\n\n#### Modifying Flags\n\nIn rare situations, you may need the following:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang) %]\n\n\u003c!-- xgettext:no-perl-brace-format --\u003e\n[% gtx.xgettext(\"Value: {value}\", value =\u003e whatever) %]\n```\n\nNormally, the argument of `xgettext()` will be flagged in\nthe `.po` file with \"perl-brace-format\", and a translation\nwill fail to compile if the translation does not contain exactly\nthe same placeholders as the original does.\n\nYou can override that default behavior for individual messages\nby placing a comment containing the string \"xgettext:\" directly\nin front of the string.\n\n### Translation Workflow\n\nThe translation workflow is the standard workflow known from GNU \nGettext.  All files relevant for translations are conventionally\nkept in a subdirectory `po`.\n\nYou can save time if you use the seed project\n[Template-Plugin-Gettext-Seed](https://github.com/gflohr/Template-Plugin-Gettext-Seed)\nas a base.  It contains a directory `po` ready for use,\nwith --- at your choice --- a Makefile or a script `po-make.pl`\nthat automates the entire translation workflow.  It is also\nprepared for extracting strings from other sources than\ntemplate files.  In that example, these are Perl source files,\nbut it will work in a similar fashion for other programming\nlanguages.\n\nBut rolling your own version is also simple.  Just read on.\n\n#### Extracting Strings With `xgettext-tt2`\n\nExtracting translatable strings from templates for the Template\nToolkit 2 is as easy as:\n\n```shell\n$ xgettext-tt2 TEMPLATE....\n```\n\nThis will scan all files given as arguments for translatable strings\nand create a file `messages.po` with the strings found.\n\nThe normal invocation of `xgettext-tt2` is normally a little bit more\nsophisticated:\n\n```shell\n$ xgettext-tt2 --files-from=POTFILES \\\n    --output=com.mydomain.www.pot \\\n    --add-comments=TRANSLATORS: --from-code=utf-8 \\\n    --force-po\n```\n\nYou can, of course, write everyting in one line and omit the backslashes.\n\nSpecifying all input files as arguments on the command-line can\nquickly become unwieldy.  It is more common to put the list of input\nfiles into a text file, each input file on one line, and instruct\n`xgettext-tt2` to read it with the option `--files-from`.  The name\nof the file is by convention `POTFILES`.\n\nThe output file is normally a file `TEXTDOMAIN.pot`, where \n`TEXTDOMAION` is the identifier selected in the templates.  The\nreverse hostname of the server serving the rendered templates\nis a good choice.\n\nIf you want to be able to give hints to translators in the source\nfiles, you have to specify the trigger string --- normally\n\"TRANSLATORS:\" --- with the option `--add-comments`.  Specifying\nan empty string (`--add-comments=''`) instructs `xgettext-tt2` \nto copy all comments into the `.pot` file.\n\nIf your templates contain characters outside of US-ASCII, you should\nspecify the character set of the template files with the option\n`--from-code=CODESET`.\n\nThe option `--force-po` instructs `xgettext-tt2` to write an output\nfile even if no translatable strings had been found.  But this\nis a matter of taste.  Omit the option, if you prefer it.\n\n`xgettext-tt2` has a lot more options.  They are mostly compatible\nwith the ones of `xgettext` from GNU gettext for C, Perl, and\na lot more languages.  See the [documentation for GNU Gettext's\nxgettext](https://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html)\nand the documentation for [Locale::XGettext](https://github.com/gflohr/Locale-XGettext/blob/master/lib/Locale/XGettext.pod)\nfor more information.\n\nBy the way, why is the ouput file a `.pot` file and not a `.po`\nfile?  It is the *template* for the `.po` files for the individual\nlanguages.  You never edit that file, but re-generate it, whenever\nthe source files have changed.  Hence, it only contains strings\nin the original, in the base language.\n\n#### Creating Translation Files\n\nFor each supported language (except for the base language) you\nshould create a file `LL.po`, where `LL` is the two-letter\nlanguage code for that language, for example `fr.po`, `de.po`,\nor `it.po`.  You can also specify the combination of language\nand country like in `de_DE.po` or `pt_BR.po`.\n\nOne option for that is to simply copy the `.pot` file and\nedit the header accordingly.  It is normally easier to do that\nwith the program `msginit`:\n\n```\n$ msginit --input=com.mydomain.www.pot --locale=fr\n```\n\nReplace `TEXTDOMAIN.pot` with the name of the `.pot` file, and\n`fr` with the language in question.  This will prefill a lot\nof fields in the `.po` file.\n\n#### Compiling Translation Files\n\nThe translated `.po` files are compiled with the program `msgfmt`:\n\n```shell\n$ msgfmt --check --statistics --verbose -o fr.mo fr.po\nfr.po: 212 translated messages, 1 fuzzy translation, 3 untranslated messages.\n```\n\nThis will compile the translation file `fr.po` into a binary\nfile `fr.mo`.  It also checks the translations for formal errors\nand print statistics about the number of translated and\nuntranslated strings.\n\n#### Installing Translation Files\n\nThe plugin does not use `.po` files for looking up translations\nbut the binary `.mo` files.  But it has to find them.\n\nYou have to decide for one of the directories that \n`Template::Plugin::Gettext` searches for translations.  The\ndefault order is:\n\n* `@INC/LocaleData`\n* `/usr/share/locale`\n* `/usr/local/share/locale`\n\nThe first line means that every directory `LocaleDir` inside\nPerl's include directories is searched for translation files.\nKeep in mind, that for security reasons the current directory\n(`.`) is nowadays often *not* in Perl's `@INC`.\n\nLet's assume that `/var/www/lib` is in Perl's @INC.  You would\nthen install the French translation file `fr.mo` as `/var/www/lib/LocaleData/fr/LC_MESSAGES/com.mydomain.www.mo`.\n`TEXTDOMAIN` is a placeholder for the textdomain you have\nselected (and `LC_MESSAGES` is *not* a placeholder but a real \ndirectory name).\n\nThat is good except for the fact that `/var/www/lib` is usually\nnot in Perl's `@INC`.  But you can change that where you invoke\nthe template processor:\n\n```perl\nBEGIN {\n    unshift @INC, '/var/www/lib';\n}\n\nuse Template;\n\nTemplate-\u003enew-\u003eprocess('template.html', $data);\n```\n\nYou can completely override the default search order in the\ntemplates:\n\n```html\n[% USE gtx = Gettext('com.mydomain.www', lang, 'utf-8', \n                     '/var/www/locale', '/srv/www/locale')]\n```\n\nNow, the French translation would be searched in \n`/var/www/locale/fr/LC_MESSAGES/com.mydomain.www.mo` and \n`/var/www/locale/fr/LC_MESSAGES/com.mydomain.www.mo`.\n\n#### Updating Translation Files\n\nTranslations may become obsolete, when the source templates\nchange.  In this case, you have to merge the new set of \ntranslatable strings into the existing translation files.\nFortunately, GNU Gettext makes this easy:\n\n```shell\n$ xgettext-tt2 --files-from=POTFILES \\\n    --output=com.mydomain.www.pot \\\n    --add-comments=TRANSLATORS: --from-code=utf-8 \\\n    --force-po\n$ cp fr.po fr.old.po\n$ msgmerge fr.old.po com.mydomain.www.pot -o fr.po\n....... done\n```\n\nYou first update the `.pot` file with `xgettext-tt2` so that it\ncontains the current set of translatable strings.  You then\nmake a backup of each `.po` file and then invoke the program\n`msgmerge` for merging the current translations from `fr.old.po`\nwith the new set of strings from `com.mydomain.www.pot` into\nthe updated translation file `fr.po`.\n\nThe file `fr.po` will now contain the new strings as untranslated\nentries.  Strings that have only slightly change will retain their\ntranslations but they will be marked as \"fuzzy\", so that they\ncan be reviewed by a translator.  Entries for strings that are\nno longer present in the sources are obsoleted.\n\n#### Integrating With Other Programming Languages\n\nThe GNU Gettext framework is available for a lot of programming\nlanguages and it is not uncommon that two or more of these \nlanguages are mixed in a project.  It is beneficial in these\ncases to use a common translation base for all used \ntechnologies.\n\n`xgettext-tt2` is based on [`Locale-XGettext`](https://github.com/gflohr/Locale-XGettext)\nand therefore not only understands Template Toolkit templates\nbut also `.po` and `.pot` files as input.  GNU Gettext's xgettext\nhas the same feature.\n\nAccumulating all translatable strings from the different\ntechnologies is therefore very easy.  If you have a project\nthat uses Template Toolkit for rendering web pages and Perl\nfor the business logic you first extract strings from your \nPerl files --- as usual --- with `xgettext` from GNU gettext\ninto a temporary file, for example `plfiles.pot`.  Then you\nextract the strings from the templates with `xgettext-tt2` \nfrom this library, but you specify `plfiles.pot` as an \nadditional input file. And now the output file of `xgettext-tt2`\ncontains all the strings from the template files *plus* those\nfrom the Perl files in `plfiles.pot`.\n\nOf course, you can also do it the other way round, extract\nwith `xgettext-tt2` into `ttfiles.pot`, and then feed that as\nan additional input file to GNU Gettext's `xgettext`.\n\nYou can use the seed project [Template-Plugin-Gettext-Seed](https://github.com/gflohr/Template-Plugin-Gettext-Seed)\nas a fully functional starting point for such setups.\n\n## Bugs\n\nPlease report bugs at [https://github.com/gflohr/Template-Plugin-Gettext/issues](https://github.com/gflohr/Template-Plugin-Gettext/issues)\n\n## Author\n\nTemplate-Plugin-Gettext was written by [Guido Flohr](http://www.guido-flohr.net/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgflohr%2Ftemplate-plugin-gettext","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgflohr%2Ftemplate-plugin-gettext","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgflohr%2Ftemplate-plugin-gettext/lists"}