From: Alexander Ebert Date: Mon, 26 Sep 2011 18:07:33 +0000 (+0200) Subject: Implemented i18n support X-Git-Tag: 2.0.0_Beta_1~1770 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=2b4d2743039669462660788ecfb60701192f4113;p=GitHub%2FWoltLab%2FWCF.git Implemented i18n support --- diff --git a/wcfsetup/install/files/acp/templates/userGroupAdd.tpl b/wcfsetup/install/files/acp/templates/userGroupAdd.tpl index e113fcfe75..6be83ef450 100644 --- a/wcfsetup/install/files/acp/templates/userGroupAdd.tpl +++ b/wcfsetup/install/files/acp/templates/userGroupAdd.tpl @@ -4,6 +4,10 @@ // diff --git a/wcfsetup/install/files/acp/templates/userGroupList.tpl b/wcfsetup/install/files/acp/templates/userGroupList.tpl index 4b568098ce..8533495ec7 100644 --- a/wcfsetup/install/files/acp/templates/userGroupList.tpl +++ b/wcfsetup/install/files/acp/templates/userGroupList.tpl @@ -52,7 +52,7 @@ {if $additionalButtons[$group->groupID]|isset}{@$additionalButtons[$group->groupID]}{/if}

{@$group->groupID}

- {if $group->isEditable()}

{$group->groupName}{else}{$group->groupName}

{/if} + {if $group->isEditable()}

{lang}{$group->groupName}{/lang}{else}{lang}{$group->groupName}{/lang}

{/if}

{#$group->members}

{if $additionalColumns[$group->groupID]|isset}{@$additionalColumns[$group->groupID]}{/if} diff --git a/wcfsetup/install/files/js/WCF.js b/wcfsetup/install/files/js/WCF.js index cdeab028c4..852c874319 100644 --- a/wcfsetup/install/files/js/WCF.js +++ b/wcfsetup/install/files/js/WCF.js @@ -34,6 +34,24 @@ $.extend(true, { */ wcfIsset: function(id) { return !!$('#' + $.wcfEscapeID(id)).length; + }, + + /** + * Returns the length of an object. + * + * @param object targetObject + * @return integer + */ + getLength: function(targetObject) { + var $length = 0; + + for (var $key in targetObject) { + if (targetObject.hasOwnProperty($key)) { + $length++; + } + } + + return $length; } }); @@ -1532,6 +1550,277 @@ WCF.Language = { } }; +/** + * Handles multiple language input fields. + * + * @param string elementID + * @param boolean forceSelection + * @param object values + * @param object availableLanguages + */ +WCF.MultipleLanguageInput = function(elementID, forceSelection, values, availableLanguages) { this.init(elementID, forceSelection, values, availableLanguages); }; +WCF.MultipleLanguageInput.prototype = { + /** + * list of available languages + * @var object + */ + _availableLanguages: {}, + + /** + * initialization state + * @var boolean + */ + _didInit: false, + + /** + * target input element + * @var jQuery + */ + _element: null, + + /** + * enables multiple language ability + * @var boolean + */ + _isEnabled: false, + + /** + * enforce multiple language ability + * @var boolean + */ + _forceSelection: false, + + /** + * currently active language id + * @var integer + */ + _languageID: 0, + + /** + * language selection list + * @var jQuery + */ + _list: null, + + /** + * list of language values on init + * @var object + */ + _values: null, + + /** + * Initializes multiple language ability for given element id. + * + * @param integer elementID + * @param boolean forceSelection + * @param boolean isEnabled + * @param object values + * @param object availableLanguages + */ + init: function(elementID, forceSelection, values, availableLanguages) { + this._element = $('#' + $.wcfEscapeID(elementID)); + this._forceSelection = forceSelection; + this._values = values; + this._availableLanguages = availableLanguages; + + // default to current user language + this._languageID = LANGUAGE_ID; + if (this._element.length == 0) { + console.debug("[WCF.MultipleLanguageInput] element id '" + elementID + "' is unknown"); + return; + } + + // build selection handler + var $enableOnInit = ($.getLength(this._values) > 0) ? true : false; + this._prepareElement($enableOnInit); + + // listen for submit event + this._element.parents('form').submit($.proxy(this._submit, this)); + + this._didInit = true; + }, + + /** + * Builds language handler. + * + * @param boolean enableOnInit + */ + _prepareElement: function(enableOnInit) { + this._element.wrap('
'); + var $wrapper = this._element.parent(); + var $button = $('').prependTo($wrapper); + var $span = $button.children('span'); + + $span.click($.proxy(this._enable, this)); + WCF.CloseOverlayHandler.addCallback(this._element.wcfIdentify(), $.proxy(this._closeSelection, this)); + + if (enableOnInit) { + $span.trigger('click'); + + // pre-select current language + this._list.children('li').each($.proxy(function(index, listItem) { + var $listItem = $(listItem); + if ($listItem.data('languageID') == this._languageID) { + $listItem.trigger('click'); + } + }, this)); + } + }, + + /** + * Enables the language selection or shows the selection if already enabled. + * + * @param object event + */ + _enable: function(event) { + if (!this._isEnabled) { + var $button = $(event.target); + $button.addClass('active'); + + // insert list + if (this._list === null) { + this._list = $('').insertAfter($button.parent()); + this._list.click(function(event) { + // discard click event + event.stopPropagation(); + }); + + // insert available languages + for (var $languageID in this._availableLanguages) { + $('
  • ' + this._availableLanguages[$languageID] + '
  • ').data('languageID', $languageID).click($.proxy(this._changeLanguage, this)).appendTo(this._list); + } + + // disable language input + $('
  • disable i18n
  • ').click($.proxy(this._disable, this)).appendTo(this._list); + } + + this._isEnabled = true; + } + + // toggle list + if (this._list.is(':visible')) { + this._closeSelection(); + } + else { + this._showSelection(); + } + + // discard event + event.stopPropagation(); + }, + + /** + * Shows the language selection. + */ + _showSelection: function() { + if (this._isEnabled) { + // display status for each language + this._list.children('li').each($.proxy(function(index, listItem) { + var $listItem = $(listItem); + var $languageID = $listItem.data('languageID'); + + if ($languageID) { + if (this._values[$languageID] && this._values[$languageID] != '') { + $listItem.removeClass('missingValue'); + } + else { + $listItem.addClass('missingValue'); + } + } + }, this)); + + // show list + this._list.show(); + } + }, + + /** + * Closes the language selection. + */ + _closeSelection: function() { + this._list.hide(); + }, + + /** + * Changes the currently active language. + * + * @param object event + */ + _changeLanguage: function(event) { + var $button = $(event.target); + + // save current value + if (this._didInit) { + this._values[this._languageID] = this._element.val(); + } + + // set new language + this._languageID = $button.data('languageID'); + if (this._values[this._languageID]) { + this._element.val(this._values[this._languageID]); + } + else { + this._element.val(''); + } + + // update marking + this._list.children('li').removeClass('active'); + $button.addClass('active'); + + // update label + this._list.prev('.dropdownCaption').children('span').text(this._availableLanguages[this._languageID]); + + // close selection and set focus on input element + this._closeSelection(); + this._element.focus(); + }, + + /** + * Disables language selection for current element. + */ + _disable: function() { + // remove active marking + this._list.prev('.dropdownCaption').children('span').removeClass('active').text('enable i18n'); + this._closeSelection(); + + // update element value + if (this._values[LANGUAGE_ID]) { + this._element.val(this._values[LANGUAGE_ID]); + } + else { + // no value for current language found, proceed with empty input + this._element.val(); + } + + this._isEnabled = false; + }, + + /** + * Prepares language variables on before submit. + */ + _submit: function() { + // insert hidden form elements on before submit + if (!this._isEnabled) { + return 0xDEADBEAF; + } + + // fetch active value + if (this._languageID) { + this._values[this._languageID] = this._element.val(); + } + + var $form = $(this._element.parents('form')[0]); + var $elementID = this._element.wcfIdentify(); + + for (var $languageID in this._values) { + $('').appendTo($form); + } + + // remove name attribute to prevent conflict with i18n values + this._element.removeAttr('name'); + } +}; + /** * Icon collection used across all JavaScript classes. * diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupAddForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupAddForm.class.php index 650565b9bd..8d4eeaba7c 100755 --- a/wcfsetup/install/files/lib/acp/form/UserGroupAddForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupAddForm.class.php @@ -3,8 +3,10 @@ namespace wcf\acp\form; use wcf\system\menu\acp\ACPMenu; use wcf\data\user\group\UserGroup; use wcf\data\user\group\UserGroupAction; +use wcf\data\user\group\UserGroupEditor; use wcf\system\exception\UserInputException; use wcf\system\exception\SystemException; +use wcf\system\language\I18nHandler; use wcf\system\WCF; use wcf\system\WCFACP; use wcf\util\ClassUtil; @@ -72,13 +74,21 @@ class UserGroupAddForm extends AbstractOptionListForm { */ public $additionalFields = array(); + public function readParameters() { + parent::readParameters(); + + I18nHandler::getInstance()->register('groupName'); + } + /** * @see wcf\form\IForm::readFormParameters() */ public function readFormParameters() { parent::readFormParameters(); - if (isset($_POST['groupName'])) $this->groupName = StringUtil::trim($_POST['groupName']); + I18nHandler::getInstance()->readValues(); + + if (I18nHandler::getInstance()->isPlainValue('groupName')) $this->groupName = I18nHandler::getInstance()->getValue('groupName'); if (isset($_POST['activeTabMenuItem'])) $this->activeTabMenuItem = $_POST['activeTabMenuItem']; if (isset($_POST['activeSubTabMenuItem'])) $this->activeSubTabMenuItem = $_POST['activeSubTabMenuItem']; } @@ -92,7 +102,7 @@ class UserGroupAddForm extends AbstractOptionListForm { // validate group name try { - if (empty($this->groupName)) { + if (!I18nHandler::getInstance()->validateValue('groupName')) { throw new UserInputException('groupName'); } } @@ -126,6 +136,19 @@ class UserGroupAddForm extends AbstractOptionListForm { ); $groupAction = new UserGroupAction(array(), 'create', $data); $groupAction->executeAction(); + + if (!I18nHandler::getInstance()->isPlainValue('groupName')) { + $returnValues = $groupAction->getReturnValues(); + $groupID = $returnValues['returnValues']->groupID; + I18nHandler::getInstance()->save('groupName', 'wcf.acp.group.group'.$groupID, 'wcf.acp.group', 1); + + // update group name + $groupEditor = new UserGroupEditor($returnValues['returnValues']); + $groupEditor->update(array( + 'groupName' => 'wcf.acp.group.group'.$groupID + )); + } + $this->saved(); // show success message @@ -156,6 +179,8 @@ class UserGroupAddForm extends AbstractOptionListForm { public function assignVariables() { parent::assignVariables(); + I18nHandler::getInstance()->assignVariables(1); + WCF::getTPL()->assign(array( 'groupName' => $this->groupName, 'optionTree' => $this->optionTree, diff --git a/wcfsetup/install/files/lib/acp/form/UserGroupEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserGroupEditForm.class.php index dcf95a7cab..d60fe52578 100755 --- a/wcfsetup/install/files/lib/acp/form/UserGroupEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserGroupEditForm.class.php @@ -7,6 +7,7 @@ use wcf\data\user\group\UserGroupEditor; use wcf\form\AbstractForm; use wcf\system\exception\IllegalLinkException; use wcf\system\exception\PermissionDeniedException; +use wcf\system\language\I18nHandler; use wcf\system\WCF; /** @@ -66,6 +67,7 @@ class UserGroupEditForm extends UserGroupAddForm { */ public function readData() { if (!count($_POST)) { + I18nHandler::getInstance()->setOptions('groupName', $this->group->groupName, 'wcf.acp.group.group\d+'); $this->groupName = $this->group->groupName; // get default values @@ -96,6 +98,9 @@ class UserGroupEditForm extends UserGroupAddForm { public function assignVariables() { parent::assignVariables(); + $useRequestData = (count($_POST)) ? true : false; + I18nHandler::getInstance()->assignVariables(1, $useRequestData); + WCF::getTPL()->assign(array( 'groupID' => $this->group->groupID, 'action' => 'edit' @@ -129,6 +134,16 @@ class UserGroupEditForm extends UserGroupAddForm { } } } + + $this->groupName = 'wcf.acp.group.group'.$this->group->groupID; + if (I18nHandler::getInstance()->isPlainValue('groupName')) { + I18nHandler::getInstance()->remove($this->groupName, 1); + $this->groupName = I18nHandler::getInstance()->getValue('groupName'); + } + else { + I18nHandler::getInstance()->save('groupName', $this->groupName, 'wcf.acp.group', 1); + } + $data = array( 'data' => array_merge(array('groupName' => $this->groupName), $this->additionalFields), 'options' => $saveOptions diff --git a/wcfsetup/install/files/lib/system/language/I18nHandler.class.php b/wcfsetup/install/files/lib/system/language/I18nHandler.class.php new file mode 100644 index 0000000000..7b2ce6b969 --- /dev/null +++ b/wcfsetup/install/files/lib/system/language/I18nHandler.class.php @@ -0,0 +1,311 @@ + + * @package com.woltlab.wcf + * @subpackage system.language + * @category Community Framework + */ +class I18nHandler extends SingletonFactory { + /** + * list of element ids + * @var array + */ + protected $elementIDs = array(); + + /** + * list of plain values for elements + * @var array + */ + protected $plainValues = array(); + + /** + * i18n values for elements + * @var array + */ + protected $i18nValues = array(); + + /** + * element options + * @var array + */ + protected $elementOptions = array(); + + /** + * Registers a new element id, returns false if element id is already set. + * + * @param string elementID + * @return boolean + */ + public function register($elementID) { + if (in_array($elementID, $this->elementIDs)) { + return false; + } + + $this->elementIDs[] = $elementID; + return true; + } + + /** + * Reads plain and i18n values from request data. + */ + public function readValues() { + foreach ($this->elementIDs as $elementID) { + if (isset($_POST[$elementID])) { + $this->plainValues[$elementID] = $_POST[$elementID]; + continue; + } + + $i18nElementID = $elementID . '_i18n'; + if (isset($_POST[$i18nElementID]) && is_array($_POST[$i18nElementID])) { + $this->i18nValues[$elementID] = array(); + + foreach ($_POST[$i18nElementID] as $languageID => $value) { + $this->i18nValues[$elementID][$languageID] = $value; + } + + continue; + } + + throw new SystemException("Missing expected value for element id '".$elementID."'"); + } + } + + /** + * Returns true, if given element has disabled i18n functionality. + * + * @param string elementID + * @return boolean + */ + public function isPlainValue($elementID) { + if (isset($this->plainValues[$elementID])) { + return true; + } + + return false; + } + + /** + * Returns true, if given element has enabled i18n functionality. + */ + public function hasI18nValues($elementID) { + if (isset($this->i18nValues[$elementID])) { + return true; + } + + return false; + } + + /** + * Returns plain value for given element. + * + * @param string elementID + * @return string + * @see wcf\system\language\I18nHandler::isPlainValue() + */ + public function getValue($elementID) { + return $this->plainValues[$elementID]; + } + + /** + * Returns false, if element value is not empty. + * + * @param string $elementID + * @return boolean + */ + public function validateValue($elementID) { + if ($this->isPlainValue($elementID)) { + if ($this->getValue($elementID) == '') { + return false; + } + } + else { + foreach ($this->i18nValues[$elementID] as $value) { + if (empty($value)) { + return false; + } + } + } + + return true; + } + + /** + * Saves language variable for i18n. Given package id must match the associated + * packages, using PACKAGE_ID is highly discouraged as this breaks the ability + * to delete unused language items on package uninstallation using foreign keys. + * + * @param string $elementID + * @param string $languageVariable + * @param string $languageCategory + * @param integer $packageID + */ + public function save($elementID, $languageVariable, $languageCategory, $packageID) { + // get language category id + $sql = "SELECT languageCategoryID + FROM wcf".WCF_N."_language_category + WHERE languageCategory = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array($languageCategory)); + $row = $statement->fetchArray(); + $languageCategoryID = $row['languageCategoryID']; + + $languageIDs = array_keys($this->i18nValues[$elementID]); + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("languageID IN (?)", array($languageIDs)); + $conditions->add("languageItem = ?", array($languageVariable)); + $conditions->add("packageID = ?", array($packageID)); + + $sql = "SELECT languageItemID, languageID + FROM wcf".WCF_N."_language_item + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + $languageItemIDs = array(); + while ($row = $statement->fetchArray()) { + $languageItemIDs[$row['languageID']] = $row['languageItemID']; + } + + $insertLanguageIDs = $updateLanguageIDs = array(); + foreach ($languageIDs as $languageID) { + if (isset($languageItemIDs[$languageID])) { + $updateLanguageIDs[] = $languageID; + } + else { + $insertLanguageIDs[] = $languageID; + } + } + + // insert language items + if (count($insertLanguageIDs)) { + $sql = "INSERT INTO wcf".WCF_N."_language_item + (languageID, languageItem, languageItemValue, languageCategoryID, packageID) + VALUES (?, ?, ?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + + foreach ($insertLanguageIDs as $languageID) { + $statement->execute(array( + $languageID, + $languageVariable, + $this->i18nValues[$elementID][$languageID], + $languageCategoryID, + $packageID + )); + } + } + + // update language items + if (count($updateLanguageIDs)) { + $sql = "UPDATE wcf".WCF_N."_language_item + SET languageItemValue = ? + WHERE languageItemID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + + foreach ($updateLanguageIDs as $languageID) { + $statement->execute(array( + $this->i18nValues[$elementID][$languageID], + $languageItemIDs[$languageID] + )); + } + } + } + + /** + * Removes previously created i18n language variables. + * + * @param string $languageVariable + * @param integer $packageID + */ + public function remove($languageVariable, $packageID) { + $sql = "DELETE FROM wcf".WCF_N."_language_item + WHERE languageItem = ? + AND packageID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + $languageVariable, + $packageID + )); + } + + /** + * Sets additional options for elements, required if updating values. + * + * @param integer $elementID + * @param string $value + * @param string $pattern + */ + public function setOptions($elementID, $value, $pattern) { + $this->elementOptions[$elementID] = array( + 'pattern' => $pattern, + 'value' => $value + ); + } + + /** + * Assigns element values to template. Using request data once reading + * initial database data is explicitly disallowed. Restrictions on package + * id apply as explained in save(). + * + * @param integer $packageID + * @param boolean $useRequestData + */ + public function assignVariables($packageID, $useRequestData = true) { + foreach ($this->elementIDs as $elementID) { + $elementValue = ''; + $elementI18nValues = array(); + + // use POST values instead of querying database + if ($useRequestData) { + if ($this->isPlainValue($elementID)) { + $elementValue = $this->getValue($elementID); + } + else { + if ($this->hasI18nValues($elementID)) { + $elementI18nValues = $this->i18nValues[$elementID]; + } + else { + $elementI18nValues = array(); + } + } + } + else { + if (preg_match('~^'.$this->elementOptions[$elementID]['pattern'].'$~', $this->elementOptions[$elementID]['value'])) { + // use i18n values from language items + $sql = "SELECT languageID, languageItemValue + FROM wcf".WCF_N."_language_item + WHERE languageItem = ? + AND packageID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array( + $this->elementOptions[$elementID]['value'], + $packageID + )); + while ($row = $statement->fetchArray()) { + $elementI18nValues[$row['languageID']] = $row['languageItemValue']; + } + + } + else { + // use data provided by setOptions() + $elementValue = $this->elementOptions[$elementID]['value']; + } + } + + WCF::getTPL()->assign(array( + 'availableLanguages' => LanguageFactory::getInstance()->getLanguages(), + $elementID => $elementValue, + $elementID.'_i18n' => $elementI18nValues + )); + } + } +}