Index: admin_templates/categories/categories_edit.tpl
===================================================================
--- admin_templates/categories/categories_edit.tpl	(revision 13321)
+++ admin_templates/categories/categories_edit.tpl	(working copy)
@@ -64,7 +64,7 @@
 			<inp2:m_RenderElement name="inp_edit_box_ml" prefix="c" field="Name" title="!la_fld_Name!" size="70"/>
 			<inp2:m_RenderElement name="inp_edit_textarea_ml" prefix="c" field="Description" title="!la_fld_Description!" rows="7" cols="70"/>
 			<inp2:m_RenderElement name="inp_edit_checkbox" prefix="c" field="AutomaticFilename" title="!la_fld_CategoryAutomaticFilename!" onchange="reflect_filename()"/>
-			<inp2:m_RenderElement name="inp_edit_box" prefix="c" field="Filename" title="!la_fld_CategoryFilename!" size="70"/>
+			<inp2:m_RenderElement name="inp_edit_box_ml" prefix="c" field="Filename" title="!la_fld_CategoryFilename!" size="70"/>
 			<inp2:m_RenderElement name="inp_edit_box" prefix="c" field="CategoryTemplate" title="!la_fld_CategoryTemplate!" size="70"/>
 			<inp2:m_RenderElement name="inp_edit_category" prefix="c" field="SymLinkCategoryId" title="la_fld_SymLinkCategoryId"/>
 
Index: kernel/application.php
===================================================================
--- kernel/application.php	(revision 13321)
+++ kernel/application.php	(working copy)
@@ -651,6 +651,34 @@
 	}
 
 	/**
+	 * Returns languages used during url building
+	 *
+	 * @return Array
+	 */
+	function getUrlLanguages()
+	{
+		static $languages = null;
+
+		if (!isset($languages)) {
+			$languages = Array (
+				'primary' => $this->GetDefaultLanguageId()
+			);
+
+			$site_domain =& $this->recallObject('site-domain.current');
+			/* @var $site_domain kDBItem */
+
+			if ($site_domain->isLoaded()) {
+				$languages['current'] = $site_domain->GetDBField('PrimaryLanguageId');
+			}
+			else {
+				$languages['current'] = $this->GetDefaultLanguageId(); // $this->GetVar('m_lang');
+			}
+		}
+
+		return $languages;
+	}
+
+	/**
 	 * Returns item's filename that corresponds id passed. If possible, then get it from cache
 	 *
 	 * @param string $prefix
@@ -659,6 +687,19 @@
 	 */
 	function getFilename($prefix, $id, $category_id=null)
 	{
+		static $ml_mask = null;
+
+		if (!isset($ml_mask)) {
+			$languages = $this->getUrlLanguages();
+
+			if ($languages['current'] == $languages['primary']) {
+				$ml_mask = 'l' . $languages['current'] . '_%1$s AS %1$s';
+			}
+			else {
+				$ml_mask = 'IF(COALESCE(l' . $languages['current'] . '_%1$s, "") = "", l' . $languages['primary'] . '_%1$s, l' . $languages['current'] . '_%1$s) AS %1$s';
+			}
+		}
+
 		$filename = $this->getCache('filenames',  $prefix.'_'.$id);
 		if ($filename === false) {
 			$table = $this->getUnitOption($prefix, 'TableName');
@@ -671,7 +712,7 @@
 				}
 
 				// this allows to save 2 sql queries for each category
-				$sql = 'SELECT NamedParentPath, CachedCategoryTemplate, TreeLeft, TreeRight
+				$sql = 'SELECT ' . sprintf($ml_mask, 'NamedParentPath') . ', CachedCategoryTemplate, TreeLeft, TreeRight
 						FROM '.$table.'
 						WHERE '.$id_field.' = '.$this->Conn->qstr($id);
 				$category_data = $this->Conn->GetRow($sql);
@@ -684,7 +725,9 @@
 			else {
 				$resource_id = $this->Conn->GetOne('SELECT ResourceId FROM '.$table.' WHERE '.$id_field.' = '.$this->Conn->qstr($id));
 				if (is_null($category_id)) $category_id = $this->GetVar('m_cat_id');
-				$sql = 'SELECT Filename FROM '.TABLE_PREFIX.'CategoryItems WHERE ItemResourceId = '.$resource_id.' AND CategoryId = '.$category_id;
+				$sql = 'SELECT ' . sprintf($ml_mask, 'Filename') . '
+						FROM '.TABLE_PREFIX.'CategoryItems
+						WHERE ItemResourceId = '.$resource_id.' AND CategoryId = '.$category_id;
 				$filename = $this->Conn->GetOne($sql);
 
 				/*if (!$filename) {
Index: kernel/db/db_tag_processor.php
===================================================================
--- kernel/db/db_tag_processor.php	(revision 13321)
+++ kernel/db/db_tag_processor.php	(working copy)
@@ -488,6 +488,10 @@
 
 		$cache_mod_rw = $this->Application->getUnitOption($this->Prefix, 'CacheModRewrite') && $this->Application->RewriteURLs();
 
+		if ($cache_mod_rw) {
+			$languages = $this->Application->getUrlLanguages();
+		}
+
 		while (!$list->EOL())
 		{
 			$this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); // for edit/delete links using GET
@@ -498,12 +502,31 @@
 			if ($cache_mod_rw) {
 				if ($this->Prefix == 'c') {
 					// for listing subcategories in category
-					$this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $list->GetDBField('NamedParentPath'));
+					$filename = $list->GetDBField('l' . $languages['current'] . '_NamedParentPath');
+
+					if (!$filename && ($languages['current'] != $languages['primary'])) {
+						$filename = $list->GetDBField('l' . $languages['primary'] . '_NamedParentPath');
+					}
+
+					$this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $filename);
 					$this->Application->setCache('category_tree', $list->GetDBField($id_field), $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight'));
 				} else {
 					// for listing items in category
-					$this->Application->setCache('filenames', 'c_'.$list->GetDBField('CategoryId'), $list->GetDBField('CategoryFilename'));
-					$this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $list->GetDBField('Filename'));
+					$category_filename = $list->GetDBField('l' . $languages['current'] . '_CategoryFilename');
+
+					if (!$category_filename && ($languages['current'] != $languages['primary'])) {
+						$category_filename = $list->GetDBField('l' . $languages['primary'] . '_CategoryFilename');
+					}
+
+					$this->Application->setCache('filenames', 'c_'.$list->GetDBField('CategoryId'), $category_filename);
+
+					$filename = $list->GetDBField('l' . $languages['current'] . '_Filename');
+
+					if (!$filename && ($languages['current'] != $languages['primary'])) {
+						$filename = $list->GetDBField('l' . $languages['primary'] . '_Filename');
+					}
+
+					$this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $filename);
 				}
 			}
 
Index: kernel/db/dbitem.php
===================================================================
--- kernel/db/dbitem.php	(revision 13321)
+++ kernel/db/dbitem.php	(working copy)
@@ -344,6 +344,34 @@
 	}
 
 	/**
+	 * Loads object from hash (not db)
+	 *
+	 * @param Array $fields_hash
+	 * @param string $id_field
+	 */
+	function LoadFromHash($fields_hash, $id_field = null)
+	{
+		if (!isset($id_field)) {
+			$id_field = $this->IDField;
+		}
+
+		$this->Clear();
+		$id = $fields_hash[$id_field];
+
+		if ( !$this->raiseEvent('OnBeforeItemLoad', $id) ) {
+			return false;
+		}
+
+		$this->FieldValues = array_merge_recursive2($this->FieldValues, $fields_hash);
+		$this->OriginalFieldValues = $this->FieldValues;
+
+		$this->setID($id);
+		$this->raiseEvent('OnAfterItemLoad', $id);
+
+		$this->Loaded = true;
+	}
+
+	/**
 	* Builds select sql, SELECT ... FROM parts only
 	*
 	* @access public
@@ -829,30 +857,14 @@
 
 	function PopulateMultiLangFields()
 	{
-		$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
-		/* @var $ml_helper kMultiLanguageHelper */
+		foreach ($this->Fields as $field => $options) {
+			// master field is set only for CURRENT language
+			$formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
-		$lang_count = $ml_helper->getLanguageCount();
-		foreach ($this->Fields as $field => $options)
-		{
-			// master field is set only for CURRENT language
-			if (isset($options['formatter']) && $options['formatter'] == 'kMultiLanguage' && isset($options['master_field'])) {
-				if (preg_match('/^l([0-9]+)_(.*)/', $field, $regs)) {
-					$l = $regs[1];
-					$name = $regs[2];
-					// MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode, we display ML fields directly
-					// so we set it back to itself, otherwise error will not be displayed
-					$this->Fields['l' . $l . '_' . $name]['error_field'] = 'l' . $l . '_' . $name;
-					for ($i = 1; $i <= $lang_count; $i++) {
-						if ($i == $l || !$ml_helper->LanguageFound($i)) continue;
-						$f_options = $options;
-						$f_options['error_field'] = 'l' . $i . '_' . $name;  // set error field back to itself - see comment above
-						if ($i != $this->Application->GetDefaultLanguageId()) {
-							unset($f_options['required']); // all non-primary language field set to non-required
-						}
-						$this->Fields['l' . $i . '_' . $name] = $f_options;
-					}
-				}
+			if (($formatter == 'kMultiLanguage') && array_key_exists('master_field', $options) && array_key_exists('error_field', $options)) {
+				// MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode,
+				// we display ML fields directly so we set it back to itself, otherwise error will not be displayed
+				unset($this->Fields[$field]['error_field']);
 			}
 		}
 	}
Index: kernel/kbase.php
===================================================================
--- kernel/kbase.php	(revision 13321)
+++ kernel/kbase.php	(working copy)
@@ -314,7 +314,9 @@
 	{
 		$fields = $this->getCalculatedFields($aggregated);
 		if ($fields) {
-			$sql = Array();
+			$sql = Array ();
+			$fields = str_replace('%2$s', $this->Application->GetVar('m_lang'), $fields);
+
 			foreach ($fields as $field_name => $field_expression) {
 				$sql[] = '('.$field_expression.') AS `'.$field_name.'`';
 			}
Index: kernel/utility/formatters/multilang_formatter.php
===================================================================
--- kernel/utility/formatters/multilang_formatter.php	(revision 13321)
+++ kernel/utility/formatters/multilang_formatter.php	(working copy)
@@ -4,6 +4,20 @@
 {
 
 	/**
+	 * Multilanguage helper
+	 *
+	 * @var kMultiLanguageHelper
+	 */
+	var $helper = null;
+
+	function kMultiLanguage()
+	{
+		parent::kBase();
+
+		$this->helper =& $this->Application->recallObject('kMultiLanguageHelper');
+	}
+
+	/**
 	 * Returns ML field equivalent to field name specifed
 	 *
 	 * @param string $field_name
@@ -18,9 +32,20 @@
 		return 'l'.$lang.'_'.$field_name;
 	}
 
+	/**
+	 * The method is supposed to alter config options or cofigure object in some way based on its usage of formatters
+	 * The methods is called for every field with formatter defined when configuring item.
+	 * Could be used for adding additional VirtualFields to an object required by some special Formatter
+	 *
+	 * @param string $field_name
+	 * @param array $field_options
+	 * @param kDBBase $object
+	 */
 	function PrepareOptions($field_name, &$field_options, &$object)
 	{
-		if (getArrayValue($object->Fields, $field_name, 'master_field')) return;
+		if (getArrayValue($field_options, 'master_field') || getArrayValue($field_options, 'options_processed')) {
+			return ;
+		}
 
 		$lang_field_name = $this->LangFieldName($field_name);
 
@@ -30,30 +55,103 @@
 			$this->Application->setUnitOption($object->Prefix, 'TitleField', $lang_field_name);
 		}
 
-		//substitude fields
-		$fields = $this->Application->getUnitOption($object->Prefix, 'Fields');
-		if ( isset($fields[$field_name]) ) {
+		$primary_language_id = $this->Application->GetDefaultLanguageId();
+		$fields = $this->Application->getUnitOption($object->Prefix, 'Fields', Array ());
+		$virtual_fields = $this->Application->getUnitOption($object->Prefix, 'VirtualFields', Array ());
 
-			$fields[$lang_field_name] = $fields[$field_name];
-			$fields[$lang_field_name]['master_field'] = $field_name;
-			$fields[$lang_field_name]['error_field'] = $field_name;
-			$object->Fields[$lang_field_name] = $fields[$lang_field_name];
-			$fields[$field_name]['required'] = false;
-			$object->Fields[$field_name]['required'] = false;
+		// substitude real field
+		if (array_key_exists($field_name, $fields)) {
+			$tmp_field_options = $fields[$field_name];
+			$tmp_field_options['master_field'] = $field_name;
+			$tmp_field_options['error_field'] = $field_name;
+			$field_required = array_key_exists('required', $tmp_field_options) && $tmp_field_options['required'];
+
+			for ($language_id = 1; $language_id <= $this->helper->languageCount; $language_id++) {
+				if (!$this->helper->LanguageFound($language_id)) {
+					continue;
+				}
+
+				// make all non-primary language fields not required
+				if ($language_id != $primary_language_id) {
+					unset($tmp_field_options['required']);
+				}
+				elseif ($field_required) {
+					$tmp_field_options['required'] = $field_required;
+				}
+
+				$translated_field = 'l' . $language_id . '_' . $field_name;
+				$fields[$translated_field] = $tmp_field_options;
+				$object->Fields[$translated_field] = $tmp_field_options;
+			}
+
+			// makes original field non-required
+			unset($fields[$field_name]['required'], $object->Fields[$field_name]['required']);
+
+			// prevents real field with formatter set to be saved in db
 			$object->VirtualFields[$field_name] = $object->Fields[$field_name];
+
+			$this->Application->setUnitOption($object->Prefix, 'Fields', $fields);
 		}
-		$this->Application->setUnitOption($object->Prefix, 'Fields', $fields);
 
-		//substitude virtual fields
-		$virtual_fields = $this->Application->getUnitOption($object->Prefix, 'VirtualFields');
-		if ( isset($virtual_fields[$field_name]) ) {
-			$virtual_fields[$lang_field_name] = $virtual_fields[$field_name];
-			$virtual_fields[$lang_field_name]['master_field'] = $field_name;
-			$object->VirtualFields[$lang_field_name] = $virtual_fields[$lang_field_name];
-			$virtual_fields[$field_name]['required'] = false;
-			$object->VirtualFields[$field_name]['required'] = false;
+		// substitude virtual field
+		if (array_key_exists($field_name, $virtual_fields)) {
+			$calculated_fields = $this->Application->getUnitOption($object->Prefix, 'CalculatedFields');
+			$calculated_field_special = array_key_exists($object->Special, $calculated_fields) ? $object->Special : (array_key_exists('', $calculated_fields) ? '' : false);
+
+			$tmp_field_options = $virtual_fields[$field_name];
+			$tmp_field_options['master_field'] = $field_name;
+			$tmp_field_options['error_field'] = $field_name;
+			$field_required = array_key_exists('required', $tmp_field_options) && $tmp_field_options['required'];
+
+			for ($language_id = 1; $language_id <= $this->helper->languageCount; $language_id++) {
+				if (!$this->helper->LanguageFound($language_id)) {
+					continue;
+				}
+
+				// make all non-primary language fields not required
+				if ($language_id != $primary_language_id) {
+					unset($tmp_field_options['required']);
+				}
+				elseif ($field_required) {
+					$tmp_field_options['required'] = $field_required;
+				}
+
+				$translated_field = 'l' . $language_id . '_' . $field_name;
+				$virtual_fields[$translated_field] = $tmp_field_options;
+				$object->VirtualFields[$translated_field] = $tmp_field_options;
+
+				// substitude calculated fields associated with given virtual field
+				foreach ($calculated_fields as $special => $special_fields) {
+					if (!array_key_exists($field_name, $special_fields)) {
+						continue;
+					}
+
+					$calculated_fields[$special][$translated_field] = str_replace('%2$s', $language_id, $special_fields[$field_name]);
+
+					if ($special === $calculated_field_special) {
+						$object->CalculatedFields[$translated_field] = $calculated_fields[$special][$translated_field];
+					}
+				}
+
+				// manually copy virtual field back to fields (see kDBBase::setVirtualFields about that)
+				$object->Fields[$translated_field] = $tmp_field_options;
+			}
+
+			// remove original calculated field
+			foreach ($calculated_fields as $special => $special_fields) {
+				unset($calculated_fields[$special][$field_name]);
+			}
+
+			unset($object->CalculatedFields[$field_name]);
+
+			// save back calculated fields
+			$this->Application->setUnitOption($object->Prefix, 'CalculatedFields', $calculated_fields);
+
+			// makes original field non-required
+			unset($virtual_fields[$field_name]['required'], $object->VirtualFields[$field_name]['required']);
+
+			$this->Application->setUnitOption($object->Prefix, 'VirtualFields', $virtual_fields);
 		}
-		$this->Application->setUnitOption($object->Prefix, 'VirtualFields', $virtual_fields);
 
 		//substitude grid fields
 		$grids = $this->Application->getUnitOption($object->Prefix, 'Grids', Array());
@@ -83,6 +181,9 @@
 		$this->Application->setUnitOption($object->Prefix, 'ListSortings', $sortings);
 
 		//TODO: substitude possible language-fields sortings after changing language
+
+		$fields[$field_name]['options_processed'] = $field_options['options_processed'] = true;
+		$this->Application->setUnitOption($object->Prefix, 'Fields', $fields);
 	}
 
 	/*function UpdateSubFields($field, $value, &$options, &$object)
Index: units/categories/cache_updater.php
===================================================================
--- units/categories/cache_updater.php	(revision 13321)
+++ units/categories/cache_updater.php	(working copy)
@@ -403,7 +403,6 @@
 		{
 			$fields_hash = Array (
 									'ParentPath'				=>	'|'.implode('|', $data['parent_path']).'|',
-									'NamedParentPath'			=>	$data['system'] ? $data['file_name'] : implode('/', $data['named_path'] ),
 									'CachedCategoryTemplate'	=>	$data['category_template'],
 									'CachedDescendantCatsQty'	=>	$data['children_count'],
 									'TreeLeft' => $data['left'],
@@ -415,7 +414,9 @@
 				if (!$this->LanguageFound($i)) {
 					continue;
 				}
+
 				$fields_hash['l'.$i.'_CachedNavbar'] = implode('&|&', $data['titles'][$i]);
+				$fields_hash['l'.$i.'_NamedParentPath'] = $data['system'] ? $data['file_name'][$i] : implode('/', $data['named_path'][$i]);
 				$i++;
 			}
 
@@ -433,15 +434,17 @@
 			if ($record) {
 				$i = 1;
 				while ($i <= $this->languageCount) {
+					$filename = $record['l' . $i . '_Filename'] ? $record['l' . $i . '_Filename'] : $record['l' . $this->primaryLanguageId . '_Filename'];
+
 					$data['titles'][$i][] = $record['l'.$i.'_Name'] ? $record['l'.$i.'_Name'] : $record['l'.$this->primaryLanguageId.'_Name'];
+					$data['named_path'][$i][] = preg_replace('/^Content\\//', '', $filename);
+					$data['file_name'][$i] = $filename;
 					$i++;
 				}
+
 				$data['debug_title'] = $record['l1_Name'];
-
 				$data['parent_path'][] = $category_id;
-				$data['named_path'][] = preg_replace('/^Content\\//', '', $record['Filename']);
 				$data['system'] = $record['IsSystem'];
-				$data['file_name'] = $record['Filename'];
 
 				// it is one of the modules root category
 				$root_prefix = isset($this->root_prefixes[$category_id]) ? $this->root_prefixes[$category_id] : false;
Index: units/categories/categories_config.php
===================================================================
--- units/categories/categories_config.php	(revision 13321)
+++ units/categories/categories_config.php	(working copy)
@@ -263,7 +263,7 @@
 									            'SymLinkCategoryId'			=>	Array('type' => 'int', 'default' => NULL),
 									            'ParentId'					=>	Array('type' => 'int','not_null' => 1,'default' => 0),
 									            'Name'						=>	Array('type' => 'string', 'formatter' => 'kMultiLanguage', 'not_null' => 1, 'required' => 1, 'default' => ''),
-									            'Filename'					=>	Array('type' => 'string', 'not_null' => 1, 'default' => ''),
+									            'Filename'					=>	Array('type' => 'string', 'formatter' => 'kMultiLanguage', 'not_null' => 1, 'default' => ''),
             									'AutomaticFilename'			=>	Array('type' => 'int', 'not_null' => 1, 'default' => 1),
 									            'Description'				=>	Array('type' => 'string', 'formatter' => 'kMultiLanguage', 'required' => 0, 'default' => null),
 									            'CreatedOn'					=>	Array('type' => 'int', 'formatter' => 'kDateFormatter', 'default'=>'#NOW#', 'required' => 1, 'not_null' => 1),
@@ -278,7 +278,7 @@
 									            'ParentPath'				=>	Array('type' => 'string', 'default' => null),
 									            'TreeLeft'					=>	Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
     											'TreeRight'					=>	Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
-									            'NamedParentPath'			=>	Array('type' => 'string', 'default' => null),
+									            'NamedParentPath'			=>	Array('type' => 'string', 'formatter' => 'kMultiLanguage', 'default' => null),
 									            'MetaDescription'			=>	Array('type' => 'string', 'default' => null),
 									            'HotItem'					=>	Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (2 => 'la_Auto', 1 => 'la_Always', 0 => 'la_Never'), 'use_phrases' => 1,  'not_null' => 1, 'default' => 2),
 									            'NewItem'					=>	Array('type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (2 => 'la_Auto', 1 => 'la_Always', 0 => 'la_Never'), 'use_phrases' => 1,  'not_null' => 1, 'default' => 2),
Index: units/categories/categories_event_handler.php
===================================================================
--- units/categories/categories_event_handler.php	(revision 13321)
+++ units/categories/categories_event_handler.php	(working copy)
@@ -457,8 +457,20 @@
 		$live_object =& $this->Application->recallObject($event->Prefix.'.-item', null, Array('live_table' => true, 'skip_autoload' => true));
 		$live_object->Load($id);
 
-		$cached_fields = Array('Name', 'Filename', 'CategoryTemplate', 'ParentId', 'Priority');
+		$cached_fields = Array('CategoryTemplate', 'ParentId', 'Priority');
 
+		$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
+		/* @var $ml_helper kMultiLanguageHelper */
+
+		for ($language_id = 1; $language_id <= $ml_helper->languageCount; $language_id++) {
+			if (!$ml_helper->LanguageFound($language_id)) {
+				continue;
+			}
+
+			$cached_fields[] = 'l' . $language_id . '_Name';
+			$cached_fields[] = 'l' . $language_id . '_Filename';
+		}
+
 		foreach ($cached_fields as $cached_field) {
 			if ($live_object->GetDBField($cached_field) != $temp_object->GetDBField($cached_field)) {
 				// use session instead of REQUEST because of permission editing in category can contain
Index: units/categories/categories_item.php
===================================================================
--- units/categories/categories_item.php	(revision 13321)
+++ units/categories/categories_item.php	(working copy)
@@ -53,48 +53,60 @@
 
 			return $parent_path.$this->GetID().'|';
 		}
-		
+
 		/**
 		 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 		 *
 		 * @param string $string
+		 * @param bool $language_id
 		 * @return string
 		 */
-		function stripDisallowed($string)
+		function stripDisallowed($string, $language_id = false)
 		{
 			$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
-			/* @var $filenames_helper FilenamesHelper */
+			/* @var $filenames_helper kFilenamesHelper */
 
 			$string = $filenames_helper->replaceSequences($string);
 
-			return $this->checkAutoFilename($string);
+			return $this->checkAutoFilename($string, $language_id);
 		}
 
 		function checkFilename()
 		{
 			// System templates allow any characters in filename, because they may and will contain "/"
-			if( !$this->GetDBField('AutomaticFilename') && !$this->GetDBField('IsSystem'))
-			{
-				$filename = $this->GetDBField('Filename');
-				$this->SetDBField('Filename', $this->stripDisallowed($filename) );
+			if ( !$this->GetDBField('AutomaticFilename') && !$this->GetDBField('IsSystem') ) {
+				$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
+				/* @var $ml_helper kMultiLanguageHelper */
+
+				for ($language_id = 1; $language_id < $ml_helper->languageCount; $language_id++) {
+					if (!$ml_helper->LanguageFound($language_id)) {
+						continue;
+					}
+
+					$filename = $this->GetDBField('l' . $language_id . '_Filename');
+					$this->SetDBField('l' . $language_id . '_Filename', $this->stripDisallowed($filename, $language_id) );
+				}
 			}
 		}
 
-		function checkAutoFilename($filename)
+		function checkAutoFilename($filename, $language_id = false)
 		{
-			if(!$filename) return $filename;
+			if (!$filename) {
+				return $filename;
+			}
 
+			$filename_field = $language_id ? 'l' . $language_id . '_Filename' : 'Filename';
 			$escape_char = $this->Application->ConfigValue('FilenameSpecialCharReplacement');
-			
+
 			$item_id = !$this->GetID() ? 0 : $this->GetID();
 			$check_in_parent_cat_only = $item_id ? ' AND ParentId = '.$this->GetDBField('ParentId') : '';
 
 			// check temp table
-			$sql_temp = 'SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE Filename = '.$this->Conn->qstr($filename).$check_in_parent_cat_only;
+			$sql_temp = 'SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE ' . $filename_field . ' = ' . $this->Conn->qstr($filename) . $check_in_parent_cat_only;
 			$found_temp_ids = $this->Conn->GetCol($sql_temp);
 
 			// check live table
-			$sql_live = 'SELECT '.$this->IDField.' FROM '.$this->Application->GetLiveName($this->TableName).' WHERE Filename = '.$this->Conn->qstr($filename).$check_in_parent_cat_only;
+			$sql_live = 'SELECT '.$this->IDField.' FROM '.$this->Application->GetLiveName($this->TableName).' WHERE ' . $filename_field . ' = ' . $this->Conn->qstr($filename).$check_in_parent_cat_only;
 			$found_live_ids = $this->Conn->GetCol($sql_live);
 
 			$found_item_ids = array_unique( array_merge($found_temp_ids, $found_live_ids) );
@@ -112,8 +124,8 @@
 				}
 
 				// check live & temp table
-				$sql_temp = 'SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE (Filename = %s) AND ('.$this->IDField.' != '.$item_id.')';
-				$sql_live = 'SELECT '.$this->IDField.' FROM '.$this->Application->GetLiveName($this->TableName).' WHERE (Filename = %s) AND ('.$this->IDField.' != '.$item_id.')';
+				$sql_temp = 'SELECT '.$this->IDField.' FROM '.$this->TableName.' WHERE (' . $filename_field . ' = %s) AND ('.$this->IDField.' != '.$item_id.')';
+				$sql_live = 'SELECT '.$this->IDField.' FROM '.$this->Application->GetLiveName($this->TableName).' WHERE (' . $filename_field . ' = %s) AND ('.$this->IDField.' != '.$item_id.')';
 				while ( $this->Conn->GetOne( sprintf($sql_temp, $this->Conn->qstr($filename.$append)) ) > 0 ||
 				$this->Conn->GetOne( sprintf($sql_live, $this->Conn->qstr($filename.$append)) ) > 0 )
 				{
@@ -134,12 +146,24 @@
 		 */
 		function generateFilename()
 		{
-			if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) return false;
+			$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
+			/* @var $ml_helper kMultiLanguageHelper */
 
-			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
-			$name = $this->stripDisallowed( $this->GetDBField( $ml_formatter->LangFieldName('Name', true) ) );
+			for ($language_id = 1; $language_id <= $ml_helper->languageCount; $language_id++) {
+				if (!$ml_helper->LanguageFound($language_id)) {
+					continue;
+				}
 
-			if ( $name != $this->GetDBField('Filename') ) $this->SetDBField('Filename', $name);
+				if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('l' . $language_id . '_Filename') ) {
+					continue;
+				}
+
+				$name = $this->stripDisallowed( $this->GetDBField('l' . $language_id . '_Name'), $language_id );
+
+				if ( $name != $this->GetDBField('l' . $language_id . '_Filename') ) {
+					$this->SetDBField('l' . $language_id . '_Filename', $name);
+				}
+			}
 		}
 
 		/**
Index: units/category_items/category_items_config.php
===================================================================
--- units/category_items/category_items_config.php	(revision 13321)
+++ units/category_items/category_items_config.php	(working copy)
@@ -53,9 +53,9 @@
 												    'ItemResourceId' => Array('type' => 'int', 'not_null' => 1, 'default' => 0),
 												    'PrimaryCat' => Array('type' => 'int', 'not_null' => 1, 'default' => 0),
 												    'ItemPrefix' => Array('type' => 'string','not_null'=>1,'default'=>''),
-												    'Filename' => Array('type' => 'string','not_null'=>1,'default'=>''),
+												    'Filename' => Array('type' => 'string', 'formatter' => 'kMultiLanguage', 'not_null' => 1, 'default' => ''),
 											),
-											
+
 					'VirtualFields'	=> 	Array (
 												'CategoryName'		=>	Array(),
 												'DummyId' 			=>	Array(),
Index: units/general/cat_dbitem.php
===================================================================
--- units/general/cat_dbitem.php	(revision 13321)
+++ units/general/cat_dbitem.php	(working copy)
@@ -90,12 +90,21 @@
 
 		$ret = parent::Update($id, $system_update);
 
-		if ($ret) {
-			$filename = $this->useFilenames ? $this->GetDBField('Filename') : '';
-			$sql = 'UPDATE '.$this->CategoryItemsTable().'
-					SET Filename = '.$this->Conn->qstr($filename).'
-					WHERE ItemResourceId = '.$this->GetDBField('ResourceId');
-			$this->Conn->Query($sql);
+		if ($ret && $this->useFilenames) {
+			$fields_hash = Array ();
+
+			$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
+			/* @var $ml_helper kMultiLanguageHelper */
+
+			for ($language_id = 1; $language_id <= $ml_helper->languageCount; $language_id++) {
+				if (!$ml_helper->LanguageFound($language_id)) {
+					continue;
+				}
+
+				$fields_hash['l' . $language_id . '_Filename'] = $this->GetDBField('l' . $language_id . '_Filename');
+			}
+
+			$this->Conn->doUpdate($fields_hash, $this->CategoryItemsTable(), 'ItemResourceId = ' . $this->GetDBField('ResourceId'));
 		}
 
 		unset($this->VirtualFields['ResourceId']);
@@ -120,11 +129,21 @@
 
 	function checkFilename()
 	{
-		if( !$this->GetDBField('AutomaticFilename') )
-		{
-			$filename = $this->GetDBField('Filename');
-			$this->SetDBField('Filename', $this->stripDisallowed($filename) );
+		if ( $this->GetDBField('AutomaticFilename') ) {
+			return ;
 		}
+
+		$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
+		/* @var $ml_helper kMultiLanguageHelper */
+
+		for ($language_id = 1; $language_id < $ml_helper->languageCount; $language_id++) {
+			if (!$ml_helper->LanguageFound($language_id)) {
+				continue;
+			}
+
+			$filename = $this->GetDBField('l' . $language_id . '_Filename');
+			$this->SetDBField('l' . $language_id . '_Filename', $this->stripDisallowed($filename, $language_id) );
+		}
 	}
 
 	function Copy($cat_id=null)
@@ -252,14 +271,17 @@
 	 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 	 *
 	 * @param string $string
+	 * @param int $language_id
 	 * @return string
 	 */
-	function stripDisallowed($filename)
+	function stripDisallowed($filename, $language_id = false)
 	{
 		$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
+		/* @var $filenames_helper kFilenamesHelper */
+
 		$table = $this->mode == 't' ? $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems', 'prefix:'.$this->Prefix) : TABLE_PREFIX.'CategoryItems';
 
-		return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename);
+		return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename, $language_id);
 	}
 
 	/* commented out because it's called only from stripDisallowed body, which is moved to helper
@@ -277,16 +299,30 @@
 	 */
 	function generateFilename()
 	{
-		if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) return false;
+		$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
+		/* @var $ml_helper kMultiLanguageHelper */
 
-		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
-		if (preg_match('/l([\d]+)_(.*)/', $title_field, $regs)) {
-			// if title field is multilingual, then use it's name from primary language
-			$title_field = 'l'.$this->Application->GetDefaultLanguageId().'_'.$regs[2];
+		for ($language_id = 1; $language_id <= $ml_helper->languageCount; $language_id++) {
+			if (!$ml_helper->LanguageFound($language_id)) {
+				continue;
+			}
+
+			if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('l' . $language_id . '_Filename') ) {
+				continue;
+			}
+
+			$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
+
+			if (preg_match('/^l([\d]+)_(.*)/', $title_field, $regs)) {
+				$title_field = 'l' . $language_id . '_' . $regs[2];
+			}
+
+			$name = $this->stripDisallowed( $this->GetDBField($title_field), $language_id );
+
+			if ( $name != $this->GetDBField('l' . $language_id . '_Filename') ) {
+				$this->SetDBField('l' . $language_id . '_Filename', $name);
+			}
 		}
-		$name = $this->stripDisallowed( $this->GetDBField($title_field) );
-
-		if ( $name != $this->GetDBField('Filename') ) $this->SetDBField('Filename', $name);
 	}
 
 	/**
Index: units/general/helpers/filenames_helper.php
===================================================================
--- units/general/helpers/filenames_helper.php	(revision 13321)
+++ units/general/helpers/filenames_helper.php	(working copy)
@@ -1,7 +1,7 @@
 <?php
 
 class kFilenamesHelper extends kHelper {
-	
+
 	/**
 	 * Character, that should replace disallowed symbols in filename
 	 *
@@ -22,8 +22,7 @@
 	 * @var Array
 	 */
 	var $_filenameReplaceTo = Array ();
-	
-	
+
 	function kFilenamesHelper()
 	{
 		parent::kHelper();
@@ -66,20 +65,24 @@
 	 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 	 *
 	 * @param string $string
+	 * @param int $language_id
 	 * @return string
 	 */
-	function stripDisallowed($table, $id_field, $item_id, $filename)
+	function stripDisallowed($table, $id_field, $item_id, $filename, $language_id = false)
 	{
 		$filename = $this->replaceSequences($filename);
 
-		return $this->checkAutoFilename($table, $id_field, $item_id, $filename);
+		return $this->checkAutoFilename($table, $id_field, $item_id, $filename, $language_id);
 	}
 
-	function checkAutoFilename($table, $id_field, $item_id, $filename)
+	function checkAutoFilename($table, $id_field, $item_id, $filename, $language_id = false)
 	{
-		if(!$filename) return $filename;
+		if (!$filename) {
+			return $filename;
+		}
 
 		$item_id = !$item_id ? 0 : $item_id;
+		$filename_field = $language_id ? 'l' . $language_id . '_Filename' : 'Filename';
 
 		if ($table == TABLE_PREFIX.'CategoryItems') {
 			$item_categories_cur = $this->Conn->GetCol('SELECT CategoryId FROM '.$table.' WHERE ItemResourceId = '.$item_id);
@@ -96,12 +99,12 @@
 		}
 
 		// check current table (temp or live)
-		$sql_temp = 'SELECT '.$id_field.' FROM '.$table.' WHERE Filename = '.$this->Conn->qstr($filename).$cat_filter;
+		$sql_temp = 'SELECT '.$id_field.' FROM '.$table.' WHERE ' . $filename_field . ' = '.$this->Conn->qstr($filename).$cat_filter;
 		$found_temp_ids = $this->Conn->GetCol($sql_temp);
 
 		// check live table if current is temp
 		if ( $this->Application->IsTempTable($table) ) {
-			$sql_live = 'SELECT '.$id_field.' FROM '.$this->Application->GetLiveName($table).' WHERE Filename = '.$this->Conn->qstr($filename).$cat_filter;
+			$sql_live = 'SELECT '.$id_field.' FROM '.$this->Application->GetLiveName($table).' WHERE ' . $filename_field . ' = '.$this->Conn->qstr($filename).$cat_filter;
 			$found_live_ids = $this->Conn->GetCol($sql_live);
 		}
 		else {
@@ -123,8 +126,8 @@
 			}
 
 			// check live & temp table
-			$sql_cur = 'SELECT '.$id_field.' FROM '.$table.' WHERE (Filename = %s) AND ('.$id_field.' != '.$item_id.')'.$cat_filter;
-			$sql_live = $this->Application->IsTempTable($table) ? 'SELECT '.$id_field.' FROM '.$this->Application->GetLiveName($table).' WHERE (Filename = %s) AND ('.$id_field.' != '.$item_id.')'.$cat_filter : false;
+			$sql_cur = 'SELECT '.$id_field.' FROM '.$table.' WHERE (' . $filename_field . ' = %s) AND ('.$id_field.' != '.$item_id.')'.$cat_filter;
+			$sql_live = $this->Application->IsTempTable($table) ? 'SELECT '.$id_field.' FROM '.$this->Application->GetLiveName($table).' WHERE (' . $filename_field . ' = %s) AND ('.$id_field.' != '.$item_id.')'.$cat_filter : false;
 			while ( $this->Conn->GetOne( sprintf($sql_cur, $this->Conn->qstr($filename.$append)) ) > 0 ||
 							(	$sql_live
 								&&
Index: units/general/helpers/multilanguage.php
===================================================================
--- units/general/helpers/multilanguage.php	(revision 13321)
+++ units/general/helpers/multilanguage.php	(working copy)
@@ -71,6 +71,10 @@
 		 */
 		function getLanguageCount()
 		{
+			if ($this->languageCount > 0) {
+				return $this->languageCount;
+			}
+
 			$id_field = $this->Application->getUnitOption('lang', 'IDField');
 			$table_name = $this->Application->getUnitOption('lang', 'TableName');
 
