Refactor i18n implementation

One can now use $this->translate(msg) in a view or controller without the
need to pass the module domain (web&cli). Forms still need to be built
with _t(msg) and _mt(dom, msg).

refs #5533
This commit is contained in:
Johannes Meyer 2014-01-29 16:25:08 +01:00
parent 6cc4f1b53a
commit bd34669357
8 changed files with 199 additions and 58 deletions

View File

@ -31,13 +31,11 @@ namespace Icinga\Application;
use \DateTimeZone;
use \Exception;
use \Zend_Loader_Autoloader;
use \Icinga\Application\Modules\Manager as ModuleManager;
use \Icinga\Application\Platform;
use \Icinga\Application\Config;
use \Icinga\Exception\ProgrammingError;
use \Icinga\Exception\ConfigurationError;
use \Icinga\Util\DateTimeFactory;
use \Icinga\Util\Translator;
use Icinga\Data\ResourceFactory;
@ -427,4 +425,23 @@ abstract class ApplicationBootstrap
DateTimeFactory::setConfig(array('timezone' => $tz));
return $this;
}
/**
* Setup internationalization using gettext
*
* Uses the language defined in the global config or the default one
*
* @return self
*/
protected function setupInternationalization()
{
Translator::setupLocale($this->config->global->get('language', 'en_US'));
$localeDir = $this->getApplicationDir('locale');
if (file_exists($localeDir) && is_dir($localeDir)) {
Translator::registerDomain('icinga', $localeDir);
}
return $this;
}
}

View File

@ -31,7 +31,6 @@ namespace Icinga\Application;
use Icinga\Application\Platform;
use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Modules\Manager as ModuleManager;
use Icinga\Cli\Params;
use Icinga\Cli\Loader;
use Icinga\Cli\Screen;
@ -63,12 +62,12 @@ class Cli extends ApplicationBootstrap
{
$this->assertRunningOnCli();
$this->setupConfig()
->parseBasicParams()
->fixLoggingConfig()
->setupErrorHandling()
->setupResourceFactory()
->setupModuleManager()
;
->setupInternationalization()
->parseBasicParams()
->fixLoggingConfig()
->setupErrorHandling()
->setupResourceFactory()
->setupModuleManager();
}
protected function fixLoggingConfig()

View File

@ -35,6 +35,7 @@ use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Util\Translator;
use Icinga\Web\Hook;
/**
@ -422,7 +423,7 @@ class Module
protected function registerLocales()
{
if (file_exists($this->localedir) && is_dir($this->localedir)) {
bindtextdomain($this->name, $this->localedir);
Translator::registerDomain($this->name, $this->localedir);
}
return $this;
}

View File

@ -50,6 +50,7 @@ use Icinga\User\Preferences\SessionStore;
use Icinga\Util\DateTimeFactory;
use Icinga\Session\Session as BaseSession;
use Icinga\Web\Session;
use Icinga\Util\Translator;
/**
* Use this if you want to make use of Icinga functionality in other web projects
@ -116,10 +117,10 @@ class Web extends ApplicationBootstrap
->setupResourceFactory()
->setupSession()
->setupUser()
->setupInternationalization()
->setupTimezone()
->setupRequest()
->setupZendMvc()
->setupTranslation()
->setupModuleManager()
->loadEnabledModules()
->setupRoute()
@ -168,25 +169,6 @@ class Web extends ApplicationBootstrap
return $this->viewRenderer;
}
/**
* Load translations
*
* @return self
*/
private function setupTranslation()
{
// AuthManager::getInstance()->getSession()->language;
$locale = null;
if (!$locale) {
$locale = 'en_US';
}
putenv('LC_ALL=' . $locale . '.UTF-8');
setlocale(LC_ALL, $locale . '.UTF-8');
bindtextdomain('icinga', $this->getApplicationDir() . '/locale');
textdomain('icinga');
return $this;
}
/**
* Dispatch public interface
*/
@ -449,4 +431,30 @@ class Web extends ApplicationBootstrap
DateTimeFactory::setConfig(array('timezone' => $tz));
return $this;
}
/**
* Setup internationalization using gettext
*
* Uses the preferred user language or the configured default and system default, respectively.
*
* @return self
*/
protected function setupInternationalization()
{
parent::setupInternationalization();
$userLocale = $this->user === null ? null : $this->user->getPreferences()->get('app.language');
if ($userLocale) {
try {
Translator::setupLocale($userLocale);
} catch (Exception $error) {
Logger::error(
'Cannot set locale "' . $userLocale . '" configured in ' .
'preferences of user "' . $this->user->getUsername() . '"'
);
}
}
return $this;
}
}

View File

@ -27,31 +27,25 @@
*/
// {{{ICINGA_LICENSE_HEADER}}}
if (function_exists('_')) {
function t($messageId = null)
use \Icinga\Util\Translator;
if (extension_loaded('gettext')) {
function t($messageId)
{
$msg = _($messageId);
if (! $msg) {
return $messageId;
}
return $msg;
return Translator::translate($messageId, 'icinga');
}
function mt($domain, $messageId = null)
function mt($domain, $messageId)
{
$msg = dgettext($domain, $messageId);
if (! $msg) {
return $messageId;
}
return $msg;
return Translator::translate($messageId, $domain);
}
} else {
function t($messageId = null)
function t($messageId)
{
return $messageId;
}
function mt($domain, $messageId = null)
function mt($domain, $messageId)
{
return $messageId;
}

View File

@ -2,8 +2,8 @@
namespace Icinga\Cli;
use Icinga\Cli\Loader;
use Icinga\Cli\Screen;
use Icinga\Util\Translator;
use Icinga\Application\ApplicationBootstrap as App;
use Exception;
@ -48,6 +48,21 @@ abstract class Command
return $this->trace;
}
/**
* Translate a string
*
* Autoselects the module domain, if any, and falls back to the global one if no translation could be found.
*
* @param string $text The string to translate
*
* @return string The translated string
*/
public function translate($text)
{
$domain = $this->moduleName === null ? 'icinga' : $this->moduleName;
return Translator::translate($text, $domain);
}
public function fail($msg)
{
throw new Exception($msg);

View File

@ -0,0 +1,108 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2014 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2014 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Util;
use \Exception;
/**
* Helper class to ease internationalization when using gettext
*/
class Translator
{
/**
* The default gettext domain used as fallback
*/
const DEFAULT_DOMAIN = 'icinga';
/**
* Known gettext domains
*
* @var array
*/
private static $knownDomains = array();
/**
* Translate a string
*
* Falls back to the default domain in case the string cannot be translated using the given domain
*
* @param string $text The string to translate
* @param string $domain The primary domain to use
*
* @return string The translated string
*
* @throws Exception In case the given domain is unknown
*/
public static function translate($text, $domain)
{
if ($domain !== self::DEFAULT_DOMAIN && !in_array($domain, self::$knownDomains)) {
throw new Exception("Cannot translate string '$text' with unknown domain '$domain'");
}
$res = dgettext($domain, $text);
if ($res === $text && $domain !== self::DEFAULT_DOMAIN) {
return dgettext(self::DEFAULT_DOMAIN, $text);
}
return $res;
}
/**
* Register a new gettext domain
*
* @param string $name The name of the domain to register
* @param string $directory The directory where message catalogs can be found
*
* @throws Exception In case the domain was not successfully registered
*/
public static function registerDomain($name, $directory)
{
if (bindtextdomain($name, $directory) === false) {
throw new Exception("Cannot register domain '$name' with path '$directory'");
}
bind_textdomain_codeset($name, 'UTF-8');
self::$knownDomains[] = $name;
}
/**
* Set the locale to use
*
* @param string $localeName The name of the locale to use
*
* @throws Exception In case the locale's name is invalid
*/
public static function setupLocale($localeName)
{
if (setlocale(LC_ALL, $localeName . '.UTF-8') === false) {
throw new Exception("Cannot set locale '$localeName.UTF-8' for category 'LC_ALL'");
}
putenv('LC_ALL=' . $localeName . '.UTF-8'); // Failsafe, Win and Unix
putenv('LANG=' . $localeName . '.UTF-8'); // Windows fix, untested
}
}

View File

@ -32,17 +32,13 @@ namespace Icinga\Web\Controller;
use \Exception;
use \Zend_Controller_Action;
use \Zend_Controller_Request_Abstract;
use \Zend_Controller_Front;
use \Zend_Controller_Response_Abstract;
use \Zend_Controller_Action_HelperBroker;
use \Zend_Layout;
use Icinga\Authentication\Manager as AuthManager;
use Icinga\Application\Benchmark;
use Icinga\Application\Config;
use Icinga\Web\Notification;
use Icinga\Util\Translator;
use Icinga\Web\Widget\Tabs;
use Icinga\Web\Url;
use Icinga\Web\Request;
/**
* Base class for all core action controllers
@ -152,15 +148,19 @@ class ActionController extends Zend_Controller_Action
}
/**
* Translate the given string with the global translation catalog
* Translate a string
*
* @param string $string The string that should be translated
* Autoselects the module domain, if any, and falls back to the global one if no translation could be found.
*
* @return string
* @param string $text The string to translate
*
* @return string The translated string
*/
public function translate($string)
public function translate($text)
{
return t($string);
$module = $this->getRequest()->getModuleName();
$domain = $module === 'default' ? 'icinga' : $module;
return Translator::translate($text, $domain);
}
/**
@ -212,7 +212,6 @@ class ActionController extends Zend_Controller_Action
$this->_helper->Redirector->gotoUrlAndExit($url);
}
/**
* Detect whether the current request requires changes in the layout and apply them before rendering
*