{"id":411347,"date":"2024-06-29T21:54:50","date_gmt":"2024-06-29T21:54:50","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=411347"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=411347","title":{"rendered":"<span>Simple text markup internationalization (i18n)<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/xr\/jz\/pb\/xrjzpblczowgylzxmvefrjjt4og.jpeg\" alt=\"xrjzpblczowgylzxmvefrjjt4og\" data-src=\"https:\/\/habrastorage.org\/webt\/xr\/jz\/pb\/xrjzpblczowgylzxmvefrjjt4og.jpeg\" data-blurred=\"true\"\/><\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h1 id=\"introduction\">Introduction<\/h1>\n<p>  <\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/post\/599775\/\">\u041f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u0440\u0443\u0441\u0441\u043a\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438<\/a><\/p>\n<p>  <\/p>\n<p>Several years ago my colleague posted an <a href=\"https:\/\/dzone.com\/articles\/presentation-as-code-why-i-abandoned-powerpoint\" rel=\"nofollow noopener noreferrer\">article<\/a> about making presentations in <a href=\"https:\/\/asciidoctor.org\/\" rel=\"nofollow noopener noreferrer\">Asciidoctor<\/a>. Since then, we don&#8217;t use any other approach.<\/p>\n<p>  <\/p>\n<p>Some time ago there have appeared a problem of translating presentations into several languages and have them synchronized. The solution appeared to be so simple and mature that I decided to describe it in this post.<\/p>\n<p>  <\/p>\n<p>This solution is syntax-unaware. This means, it doesn&#8217;t matter whether we use Asciidoc, other light text markup or even mixed format (with my favourite Plantuml or any other diagrams, for example). The serious limitations of this approach are (1) the translator shouldn&#8217;t break markup structure and (2) we can&#8217;t directly apply machine translation to the original text.<\/p>\n<p>  <\/p>\n<p>The solution uses <a href=\"http:\/\/docs.translatehouse.org\/projects\/translate-toolkit\/en\/latest\/\" rel=\"nofollow noopener noreferrer\">Translation Toolkit<\/a> and standard <a href=\"https:\/\/www.gnu.org\/software\/gettext\/\" rel=\"nofollow noopener noreferrer\">GNU Gettext tools<\/a>.<\/p>\n<p>  <\/p>\n<p>To make this solution clear this article has English and Russian versions. <a href=\"https:\/\/github.com\/fiddlededee\/asciidoc-i18n\" rel=\"nofollow noopener noreferrer\">Its repository<\/a> contains some simple automation that synchronizes translation, creates printing version (pdf, docx, odt) and creates Markdown file for publishing to Habr.<\/p>\n<p>  <\/p>\n<p>In my recent article on testing documentation I didn&#8217;t pay much attention to text linters, because the focus was on approaches, not exact tools. Still these tools are great. To fill the gap, I&#8217;ll use <a href=\"https:\/\/github.com\/errata-ai\/vale\" rel=\"nofollow noopener noreferrer\">vale<\/a> for this article.<\/p>\n<p>  <\/p>\n<h1 id=\"the-idea\">The idea<\/h1>\n<p>  <\/p>\n<p>Gettext assumes that key strings for translation are original messages.<\/p>\n<p>  <\/p>\n<p>Gettext uses files with <code>.po<\/code> extension (PO\u2009\u2014\u2009<a href=\"https:\/\/www.gnu.org\/software\/gettext\/manual\/html_node\/PO-Files.html#PO-Files\" rel=\"nofollow noopener noreferrer\">Portable Object<\/a>) to keep both original and translated messages. A great number of editors allow to edit such files either in a single user or collaborative environments.<\/p>\n<p>  <\/p>\n<p>The idea of Translation Toolkit is to use blocks of adjacent lines as such strings.<\/p>\n<p>  <\/p>\n<p>Take this example:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">.Winter is<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">* snow * frost<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">* Christmas * New Year<\/code><\/pre>\n<p>  <\/p>\n<p>It has three blocks of adjacent strings. Translation Toolkit will extract three key strings for translation and will put it to a file with the <code>.pot<\/code> extension (<code>.pot<\/code> stands for <code>.po<\/code> Template).<\/p>\n<p>  <\/p>\n<p>Putting or removing line breaks in this example you may make any number of strings from 1 to 5. This depends on convenience to the translator.<\/p>\n<p>  <\/p>\n<p>Using a <code>.pot<\/code> file as a template Gettext creates (updates) <code>.po<\/code> files for all required languages. Translators handle exactly these files. After that Translation Toolkit takes (1) a <code>.po<\/code> file with translation, (2) the original file and creates a final translated file.<\/p>\n<p>  <\/p>\n<h1 id=\"the-process\">The Process<\/h1>\n<p>  <\/p>\n<p>The process consists of the following steps.<\/p>\n<p>  <\/p>\n<ul>\n<li>\n<p>Initial steps to get initial translations in one or several languages.<\/p>\n<p>  <\/li>\n<li>\n<p>Updating translation steps to synchronize translation with the modified original text.<\/p>\n<p>  <\/li>\n<\/ul>\n<p>  <\/p>\n<p>The following diagrams assume that the original file is <code>i18n-adoc.adoc<\/code> and the translation goes to <code>i18n-adoc-ru.adoc<\/code>.<\/p>\n<p>  <\/p>\n<h2 id=\"initial-steps\">Initial steps<\/h2>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/0u\/rs\/zp\/0urszpwyr0va0baicqejdrhr7vq.png\" alt=\"Initial steps\" data-src=\"https:\/\/habrastorage.org\/webt\/0u\/rs\/zp\/0urszpwyr0va0baicqejdrhr7vq.png\"\/><\/p>\n<p>  <\/p>\n<p>There is a vast number of editors for translating <code>.po<\/code> files. The following screenshot shows <a href=\"https:\/\/wiki.gnome.org\/Apps\/Gtranslator\" rel=\"nofollow noopener noreferrer\">Gtranslator<\/a> interface. I prefer <a href=\"https:\/\/poedit.net\/\" rel=\"nofollow noopener noreferrer\">Poedit<\/a>, although the way it replaces backtick with tick while applying machine translation is annoying.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/xe\/yp\/8q\/xeyp8qzjmsdohjrmrccmoojupgo.png\" alt=\"Translating in Gtranslator\" data-src=\"https:\/\/habrastorage.org\/webt\/xe\/yp\/8q\/xeyp8qzjmsdohjrmrccmoojupgo.png\"\/><\/p>\n<p>  <\/p>\n<h2 id=\"updating-translation\">Updating translation<\/h2>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/um\/ff\/n3\/umffn3yakhg2w72zm4xwbos_rki.png\" alt=\"Updating translation\" data-src=\"https:\/\/habrastorage.org\/webt\/um\/ff\/n3\/umffn3yakhg2w72zm4xwbos_rki.png\"\/><\/p>\n<p>  <\/p>\n<h1 id=\"some-notes\">Some notes<\/h1>\n<p>  <\/p>\n<ol>\n<li>\n<p>In our documentation, we often reuse source i18n strings just to be sure that names of interface elements in documentation are equal to the same names in our application. We generate these i18n strings automatically in the following format:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">:main-menu-documents: Documents :main-menu-documents-my: My ...<\/code><\/pre>\n<p>  <\/p>\n<p>We include such a file in the Asciidoc document like <code>include i17n-{lang}.adoc[]<\/code>. Now there is no need to use attributes. Just translate <code>include i17n-en.adoc[]<\/code> to <code>include i17n-ru.adoc[]<\/code>.<\/p>\n<p>  <\/li>\n<li>\n<p>When <code>gettext<\/code> updates the <code>.po<\/code> files, it uses fuzzy search. If you slightly change the original text, you won&#8217;t lose its translation. It will be just marked as flaky.<\/p>\n<p>  <\/li>\n<li>\n<p>It&#8217;s easy to check whether translation file is up-to-date with a Gettext utility <code>msgfmt<\/code>.<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">msgfmt --statistics i18n-adoc-ru.po<\/code><\/pre>\n<p>  <\/p>\n<p>It shows the number of translated strings, the number of strings that need checking and the number of untranslated strings.<\/p>\n<p>  <\/li>\n<\/ol>\n<p>  <\/p>\n<h1 id=\"conclusion\">Conclusion<\/h1>\n<p>  <\/p>\n<ul>\n<li>\n<p>Translation Toolkit and Gettext make the process of documentation internationalization quite efficient.<\/p>\n<p>  <\/li>\n<li>\n<p>Simple text markups are not so simple. To take full advantage of it, one needs a certain level of skills. Try to give a translator the <code>.po<\/code> file. How many of them will be ready to make a translation? Or they&#8217;ll ask you for text in a more traditional format like Microsoft Word.<\/p>\n<p>  <\/li>\n<li>\n<p>Quality control: 58 translated messages, 0 errors, 0 warnings and 0 suggestions in 1 file.<\/p>\n<p>  <\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/599437\/\"> https:\/\/habr.com\/ru\/articles\/599437\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/xr\/jz\/pb\/xrjzpblczowgylzxmvefrjjt4og.jpeg\" alt=\"xrjzpblczowgylzxmvefrjjt4og\" data-src=\"https:\/\/habrastorage.org\/webt\/xr\/jz\/pb\/xrjzpblczowgylzxmvefrjjt4og.jpeg\" data-blurred=\"true\"\/><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-411347","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/411347","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=411347"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/411347\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=411347"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=411347"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=411347"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}