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 \DateTimeZone;
use \Exception; use \Exception;
use \Zend_Loader_Autoloader;
use \Icinga\Application\Modules\Manager as ModuleManager; use \Icinga\Application\Modules\Manager as ModuleManager;
use \Icinga\Application\Platform;
use \Icinga\Application\Config; use \Icinga\Application\Config;
use \Icinga\Exception\ProgrammingError;
use \Icinga\Exception\ConfigurationError; use \Icinga\Exception\ConfigurationError;
use \Icinga\Util\DateTimeFactory; use \Icinga\Util\DateTimeFactory;
use \Icinga\Util\Translator;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
@ -427,4 +425,23 @@ abstract class ApplicationBootstrap
DateTimeFactory::setConfig(array('timezone' => $tz)); DateTimeFactory::setConfig(array('timezone' => $tz));
return $this; 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\Platform;
use Icinga\Application\ApplicationBootstrap; use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Modules\Manager as ModuleManager;
use Icinga\Cli\Params; use Icinga\Cli\Params;
use Icinga\Cli\Loader; use Icinga\Cli\Loader;
use Icinga\Cli\Screen; use Icinga\Cli\Screen;
@ -63,12 +62,12 @@ class Cli extends ApplicationBootstrap
{ {
$this->assertRunningOnCli(); $this->assertRunningOnCli();
$this->setupConfig() $this->setupConfig()
->parseBasicParams() ->setupInternationalization()
->fixLoggingConfig() ->parseBasicParams()
->setupErrorHandling() ->fixLoggingConfig()
->setupResourceFactory() ->setupErrorHandling()
->setupModuleManager() ->setupResourceFactory()
; ->setupModuleManager();
} }
protected function fixLoggingConfig() protected function fixLoggingConfig()

View File

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

View File

@ -50,6 +50,7 @@ use Icinga\User\Preferences\SessionStore;
use Icinga\Util\DateTimeFactory; use Icinga\Util\DateTimeFactory;
use Icinga\Session\Session as BaseSession; use Icinga\Session\Session as BaseSession;
use Icinga\Web\Session; use Icinga\Web\Session;
use Icinga\Util\Translator;
/** /**
* Use this if you want to make use of Icinga functionality in other web projects * Use this if you want to make use of Icinga functionality in other web projects
@ -116,10 +117,10 @@ class Web extends ApplicationBootstrap
->setupResourceFactory() ->setupResourceFactory()
->setupSession() ->setupSession()
->setupUser() ->setupUser()
->setupInternationalization()
->setupTimezone() ->setupTimezone()
->setupRequest() ->setupRequest()
->setupZendMvc() ->setupZendMvc()
->setupTranslation()
->setupModuleManager() ->setupModuleManager()
->loadEnabledModules() ->loadEnabledModules()
->setupRoute() ->setupRoute()
@ -168,25 +169,6 @@ class Web extends ApplicationBootstrap
return $this->viewRenderer; 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 * Dispatch public interface
*/ */
@ -449,4 +431,30 @@ class Web extends ApplicationBootstrap
DateTimeFactory::setConfig(array('timezone' => $tz)); DateTimeFactory::setConfig(array('timezone' => $tz));
return $this; 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}}} // {{{ICINGA_LICENSE_HEADER}}}
if (function_exists('_')) { use \Icinga\Util\Translator;
function t($messageId = null)
if (extension_loaded('gettext')) {
function t($messageId)
{ {
$msg = _($messageId); return Translator::translate($messageId, 'icinga');
if (! $msg) {
return $messageId;
}
return $msg;
} }
function mt($domain, $messageId = null) function mt($domain, $messageId)
{ {
$msg = dgettext($domain, $messageId); return Translator::translate($messageId, $domain);
if (! $msg) {
return $messageId;
}
return $msg;
} }
} else { } else {
function t($messageId = null) function t($messageId)
{ {
return $messageId; return $messageId;
} }
function mt($domain, $messageId = null) function mt($domain, $messageId)
{ {
return $messageId; return $messageId;
} }

View File

@ -2,8 +2,8 @@
namespace Icinga\Cli; namespace Icinga\Cli;
use Icinga\Cli\Loader;
use Icinga\Cli\Screen; use Icinga\Cli\Screen;
use Icinga\Util\Translator;
use Icinga\Application\ApplicationBootstrap as App; use Icinga\Application\ApplicationBootstrap as App;
use Exception; use Exception;
@ -48,6 +48,21 @@ abstract class Command
return $this->trace; 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) public function fail($msg)
{ {
throw new Exception($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 \Exception;
use \Zend_Controller_Action; use \Zend_Controller_Action;
use \Zend_Controller_Request_Abstract; use \Zend_Controller_Request_Abstract;
use \Zend_Controller_Front;
use \Zend_Controller_Response_Abstract; use \Zend_Controller_Response_Abstract;
use \Zend_Controller_Action_HelperBroker; use \Zend_Controller_Action_HelperBroker;
use \Zend_Layout;
use Icinga\Authentication\Manager as AuthManager; use Icinga\Authentication\Manager as AuthManager;
use Icinga\Application\Benchmark; use Icinga\Application\Benchmark;
use Icinga\Application\Config; use Icinga\Util\Translator;
use Icinga\Web\Notification;
use Icinga\Web\Widget\Tabs; use Icinga\Web\Widget\Tabs;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Request;
/** /**
* Base class for all core action controllers * 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); $this->_helper->Redirector->gotoUrlAndExit($url);
} }
/** /**
* Detect whether the current request requires changes in the layout and apply them before rendering * Detect whether the current request requires changes in the layout and apply them before rendering
* *