{"id":230,"date":"2016-05-23T10:30:06","date_gmt":"2016-05-23T08:30:06","guid":{"rendered":"http:\/\/phraseapp.com\/blog\/?p=230"},"modified":"2023-09-26T11:48:33","modified_gmt":"2023-09-26T09:48:33","slug":"php-how-to-translate-php-apps","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/","title":{"rendered":"How to Master the Translation of PHP Apps"},"content":{"rendered":"<p style=\"text-align: left;\">Despite alternative web programming languages like Ruby (on Rails), Python or NodeJS dominating the industry headlines, PHP remains a popular choice for building web applications.<\/p>\n<p style=\"text-align: left;\">Despite this fact, there is still no real standard solution for localizing PHP applications.<\/p>\n<p style=\"text-align: left;\">Building a truly international application is not just about translating strings. Other issues to consider are date and time formats, currency symbols and pluralization. Programmers often underestimate the complexity of <a href=\"https:\/\/phrase.com\/blog\/posts\/localization-management\/\">localization<\/a> and get stuck with homemade code that is a pain to maintain. So, let\u2019s talk about PHP Arrays, gettext, frameworks, and Intl.<\/p>\n<p style=\"text-align: left;\">\n<style type=\"text\/css\"><!--td {border: 1px solid #ccc;}br {mso-data-placement:same-cell;}--><\/style>\n<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_69_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#php-arrays\" title=\"PHP Arrays\">PHP Arrays<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#gettext\" title=\"gettext\">gettext<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#basic-setup\" title=\"Basic Setup\">Basic Setup<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#usage\" title=\"Usage\">Usage<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#frameworks\" title=\"Frameworks\">Frameworks<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#4-intl\" title=\"4. Intl\">4. Intl<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/blog\/posts\/php-how-to-translate-php-apps\/#conclusion\" title=\"Conclusion\">Conclusion<\/a><\/li><\/ul><\/nav><\/div>\n<h2 id=\"a-namearrays1-php-arraysa\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"php-arrays\"><\/span><a name=\"arrays\"><\/a>PHP Arrays<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p style=\"text-align: left;\">Associative Arrays have been the primitive approach of many big and small PHP projects for a long time. Translatable strings are stored in an associative array, one file per language.<\/p>\n<pre class=\"lang:php decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;?php\n\/* en.php - english language file *\/\n$messages['hello'] = 'Hello';\n$messages['signup'] = 'Sign up for free';\n?&gt;<\/pre>\n<pre class=\"lang:php decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;?php\n\/* de.php - german language file *\/\n$messages['hello'] = 'Hallo';\n$messages['signup'] = 'Kostenlos registrieren';\n?&gt;<\/pre>\n<p style=\"text-align: left;\">The appropriate language file is loaded at the beginning of your application code:<\/p>\n<pre class=\"lang:php decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;?php\n    require('de.php');\n    echo($messages['hello']);\n    echo($messages['signup']);\n?&gt;<\/pre>\n<p style=\"text-align: left;\">This approach is simple and easy to use, but it quickly reaches its limits. Consider an application that displays a notification when the user uploads files:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">3 files uploaded successfully<\/pre>\n<p style=\"text-align: left;\">What if it was only one file? Maybe do this:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">1 file(s) uploaded successfully\n<\/pre>\n<p style=\"text-align: left;\">Nah! That\u2019s just plain ugly. I could come up with a hack like:<\/p>\n<pre class=\"lang:php decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;?php\nif($number_of_files == 1 ) {\n  echo $number_of_files.messages['upload_success_single'];\n  \/\/ 1 file uploaded successfully\n} else {\n  echo $number_of_files.messages['upload_success_multiple'];\n  \/\/ 4 files uploaded successfully\n}\n?&gt;<\/pre>\n<p style=\"text-align: left;\">Not only does this clutter up the code, it doesn\u2019t really solve the problem. In the English language appending a \u2018s\u2019 is enough, but pluralization works differently in other languages. For example, the pluralization of the world \u201cfile\u201d (plik) in the Polish language works like this:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">1 plik\n2,3,4 pliki\n5-21 pliko'w\n22-24 pliki\n25-31 pliko'w<\/pre>\n<h2 id=\"a-namegettext2-gettexta\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"gettext\"><\/span><a name=\"gettext\"><\/a>gettext<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p style=\"text-align: left;\">The <a href=\"https:\/\/www.gnu.org\/software\/gettext\/\">GNU gettext system<\/a> has been around for more than 20 years. It is widely used and is the de-facto standard for localization in many programming languages.<\/p>\n<p style=\"text-align: left;\">Using gettext with PHP can be tricky in some setups.<\/p>\n<p style=\"text-align: left;\">If you are running a stock Ubuntu VPS, gettext will only support the locales installed on the machine. Or perhaps you are on a hosting plan where the gettext extension isn\u2019t available.<\/p>\n<p style=\"text-align: left;\">In both cases, <a href=\"https:\/\/launchpad.net\/php-gettext\/\">php-gettext<\/a> can help. php-gettext it is a drop-in replacement for PHP enviroments where the gettext extension isn\u2019t installed.<\/p>\n<h3 id=\"basic-setup\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"basic-setup\"><\/span>Basic Setup<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p style=\"text-align: left;\">In this example, i want to use English and German. I create the following directory structure:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">index.php\n    \/locale\n        \/en_US\n            \/LC_MESSAGES\n                messages.po\n                messages.mo\n        \/de_DE\n            \/LC_MESSAGES\n                messages.po\n                messages.mo<\/pre>\n<p style=\"text-align: left;\"><a href=\"https:\/\/phrase.com\/blog\/posts\/translation\/\">Translations<\/a> are stored in .po files, a simple plain-text file format. Using just a text editor I create en\/LC_MESSAGES\/messages.po:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">msgid \"hello\"\nmsgstr \"Hello\"\nmsgid \"signup\"\nmsgstr \"Sign up for free\"<\/pre>\n<p style=\"text-align: left;\">I also create a messages.po for the de_DE locale:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">msgid \"hello\"\nmsgstr \"Hallo\"\nmsgid \"signup\"\nmsgstr \"Kostenlos registrieren\"<\/pre>\n<p style=\"text-align: left;\">In the next step, .po files are compiled to .mo files. This can be done using the msgfmt command line utility:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">msgfmt messages.po -o messages.mo<\/pre>\n<p style=\"text-align: left;\">This is done for each .po file.<\/p>\n<h3 id=\"usage\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"usage\"><\/span>Usage<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p style=\"text-align: left;\">I can now use the .mo files via gettext in my PHP app:<\/p>\n<pre class=\"lang:php decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;?php\n    $language = \"de_DE\";\n    putenv(\"LANG=\".$language);\n    setlocale(LC_ALL, $language);\n    $domain = \"messages\";\n    bindtextdomain($domain, \"locale\");\n    textdomain($domain);\n    echo gettext(\"hello\");\n    \/* echo _(\"hello\"); \/\/ Hint: _() is equal to gettext() *\/\n?&gt;<\/pre>\n<p style=\"text-align: left;\">This script creates a new gettext enviroment using the de_DE locale. Then the message with the id \u2018hello\u2019 is echo\u2019ed which will output the german \u201cHallo\u201d.<\/p>\n<p style=\"text-align: left;\">The gettext system supports plural forms. Using the example from above, I create a plural msgid:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\"><code>m<\/code><\/pre>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">sgid \"upload\"\nmsgid_plural \"uploads\"\nmsgstr[0] \"file uploaded successfully\"\nmsgstr[1] \"files uploaded successfully\"<\/pre>\n<p style=\"text-align: left;\">which I can use in my app code:<\/p>\n<pre class=\"lang:php decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;?php\n  echo $number_of_files.\" \".ngettext('upload', 'uploads', $number_of_files);\n?&gt;<\/pre>\n<p style=\"text-align: left;\">gettext can handle pluralization but it has no tools for working with <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">numbers<\/a>, currency, date\/time formats.<\/p>\n<h2 id=\"a-nameframeworks3-frameworksa\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"frameworks\"><\/span><a name=\"frameworks\"><\/a>Frameworks<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p style=\"text-align: left;\">All major PHP frameworks have built-in support for creating translations. Some offer additional features such as classes for currency and date\/time formatting.<\/p>\n<table class=\"table alignleft\">\n<thead>\n<tr>\n<th align=\"center\"><\/th>\n<th align=\"center\">Language files<\/th>\n<th align=\"center\">Plurals<\/th>\n<th align=\"center\">Date\/Time<\/th>\n<th align=\"center\">Currency<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td align=\"center\">Symfony<\/td>\n<td align=\"center\">YAML, XLIFF, PHP Arrays<\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<\/tr>\n<tr>\n<td align=\"center\">F3<\/td>\n<td align=\"center\">PHP Arrays, INI<\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<\/tr>\n<tr>\n<td align=\"center\">CodeIgniter<\/td>\n<td align=\"center\">PHP Arrays<\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<\/tr>\n<tr>\n<td align=\"center\">Kohana<\/td>\n<td align=\"center\">PHP Arrays<\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<\/tr>\n<tr>\n<td align=\"center\">CakePHP<\/td>\n<td align=\"center\">gettext<\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<\/tr>\n<tr>\n<td align=\"center\">Zend<\/td>\n<td align=\"center\">PHP Arrays, CSV, TBX\/TMX, gettext, Qt, XLIFF, INI, &#8230;<\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<td align=\"center\"><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"text-align: left;\">The localization modules of these frameworks can be used as a standalone tool without a lot of overhead code from the framework itself. Check out our other tutorials to see what that could potentially look like:<\/p>\n<ul>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/symfony-4-i18n\/\">The Ultimate Symfony Tutorial on Internationalization<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/php-multilingual-php-apps-with-fat-free-framework\/\">Creating Multilingual PHP Apps with Fat-Free Framework (F3)<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/php-i18n-with-zend-framework\/\">PHP i18n with Zend Framework<\/a><\/li>\n<\/ul>\n<ul>\n<li><\/li>\n<\/ul>\n<h2 id=\"a-nameabcd4-intla\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"4-intl\"><\/span><a name=\"abcd\"><\/a>4. Intl<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p style=\"text-align: left;\">PHP 5.3 introduces the Intl class. Intl is a set of convenient helpers for formatting dates, time, numbers and working with currency. It can be used to complement gettext or frameworks that lack some of the functionality.<\/p>\n<h2 id=\"conclusion\" style=\"text-align: left;\"><span class=\"ez-toc-section\" id=\"conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p style=\"text-align: left;\">Localization can sometimes seem challenging. Fortunately, you don\u2019t have to try to solve the problems with homemade code.<\/p>\n<p style=\"text-align: left;\">Unfortunately, gettext doesn\u2019t play very smoothly with PHP so I&#8217;d recommend using a framework.<\/p>\n<p style=\"text-align: left;\">Symfony, Zend, and F3 all do a great job and are easy to use. After playing around with all the frameworks, I really like the F3 approach. Here&#8217;s a\u00a0<a href=\"https:\/\/phrase.com\/blog\/posts\/php-multilingual-php-apps-with-fat-free-framework\/\">step-by-step guide<\/a> on getting started with F3.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to translate php apps. This post reviews the available solutions. Make your application available in various languages.<\/p>\n","protected":false},"author":61,"featured_media":2612,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":true,"_modified_date":"","_searchwp_excluded":"","footnotes":""},"categories":[40],"class_list":["post-230","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-localization"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/230"}],"collection":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/users\/61"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/comments?post=230"}],"version-history":[{"count":8,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/230\/revisions"}],"predecessor-version":[{"id":94245,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/230\/revisions\/94245"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}