浏览代码

Hello multi language mode (experimental)

markseu 11 年之前
父节点
当前提交
7ac1ef6ed1

+ 2 - 2
README.md

@@ -1,5 +1,5 @@
-Yellow 0.3.19
-=============
+Yellow 0.4.1
+============
 Yellow is for people who make websites. [Visit website](http://datenstrom.se/yellow).
 Yellow is for people who make websites. [Visit website](http://datenstrom.se/yellow).
 
 
 [![Status](https://travis-ci.org/markseu/yellowcms.svg)](https://travis-ci.org/markseu/yellowcms)
 [![Status](https://travis-ci.org/markseu/yellowcms.svg)](https://travis-ci.org/markseu/yellowcms)

+ 5 - 3
system/config/config.ini

@@ -2,9 +2,9 @@
 
 
 sitename = Yellow
 sitename = Yellow
 author = Yellow
 author = Yellow
-language = en
 style = default
 style = default
 template = default
 template = default
+language = en
 
 
 // serverScheme = http
 // serverScheme = http
 // serverName = your.domain.name
 // serverName = your.domain.name
@@ -22,6 +22,7 @@ mediaDir = media/
 styleDir = media/styles/
 styleDir = media/styles/
 imageDir = media/images/
 imageDir = media/images/
 contentDir = content/
 contentDir = content/
+contentRootDir = default/
 contentHomeDir = home/
 contentHomeDir = home/
 contentDefaultFile = page.txt
 contentDefaultFile = page.txt
 contentPagination = page
 contentPagination = page
@@ -31,13 +32,14 @@ errorPageFile = error(.*).txt
 textStringFile = text(.*).ini
 textStringFile = text(.*).ini
 parser = markdownextra
 parser = markdownextra
 parserSafeMode = 0
 parserSafeMode = 0
+multiLanguageMode = 0
 webinterfaceLocation = /edit/
 webinterfaceLocation = /edit/
 webinterfaceServerScheme = http
 webinterfaceServerScheme = http
 webinterfaceUserHashAlgorithm = bcrypt
 webinterfaceUserHashAlgorithm = bcrypt
 webinterfaceUserHashCost = 10
 webinterfaceUserHashCost = 10
 webinterfaceUserFile = user.ini
 webinterfaceUserFile = user.ini
-webinterfaceDefaultEmail =
-webinterfaceDefaultPassword =
+webinterfaceEmail =
+webinterfacePassword =
 webinterfaceNewPage = default
 webinterfaceNewPage = default
 webinterfaceFilePrefix = published
 webinterfaceFilePrefix = published
 commandlineDefaultFile = index.html
 commandlineDefaultFile = index.html

+ 2 - 2
system/core/core-commandline.php

@@ -5,7 +5,7 @@
 // Command line core plugin
 // Command line core plugin
 class YellowCommandline
 class YellowCommandline
 {
 {
-	const Version = "0.3.7";
+	const Version = "0.4.1";
 	var $yellow;				//access to API
 	var $yellow;				//access to API
 	var $content;				//number of content pages
 	var $content;				//number of content pages
 	var $media;					//number of media files
 	var $media;					//number of media files
@@ -106,7 +106,7 @@ class YellowCommandline
 		if(empty($location))
 		if(empty($location))
 		{
 		{
 			$statusCode = $this->cleanStatic($location, $path);
 			$statusCode = $this->cleanStatic($location, $path);
-			foreach($this->yellow->pages->index(true) as $page)
+			foreach($this->yellow->pages->index(true, true) as $page)
 			{
 			{
 				$statusCode = max($statusCode, $this->buildStaticLocation($page->location, $path, true));
 				$statusCode = max($statusCode, $this->buildStaticLocation($page->location, $path, true));
 			}
 			}

+ 1 - 1
system/core/core-markdownextra.php

@@ -5,7 +5,7 @@
 // Markdown extra core plugin
 // Markdown extra core plugin
 class YellowMarkdownExtra
 class YellowMarkdownExtra
 {
 {
-	const Version = "0.3.12";
+	const Version = "0.4.1";
 	var $yellow;		//access to API
 	var $yellow;		//access to API
 	
 	
 	// Handle plugin initialisation
 	// Handle plugin initialisation

+ 1 - 1
system/core/core-webinterface.css

@@ -1,4 +1,4 @@
-/* Yellow web interface 0.3.5 */
+/* Yellow web interface 0.4.1 */
 
 
 .yellow-bar { position:relative; overflow:hidden; line-height:2em; margin-bottom:10px; }
 .yellow-bar { position:relative; overflow:hidden; line-height:2em; margin-bottom:10px; }
 .yellow-bar-left { display:block; float:left; }
 .yellow-bar-left { display:block; float:left; }

+ 3 - 3
system/core/core-webinterface.js

@@ -4,7 +4,7 @@
 // Yellow main API
 // Yellow main API
 var yellow =
 var yellow =
 {
 {
-	version: "0.3.6",
+	version: "0.4.1",
 	action: function(text) { yellow.webinterface.action(text); },
 	action: function(text) { yellow.webinterface.action(text); },
 	onClick: function(e) { yellow.webinterface.hidePanesOnClick(yellow.toolbox.getEventElement(e)); },
 	onClick: function(e) { yellow.webinterface.hidePanesOnClick(yellow.toolbox.getEventElement(e)); },
 	onKeydown: function(e) { yellow.webinterface.hidePanesOnKeydown(yellow.toolbox.getEventKeycode(e)); },
 	onKeydown: function(e) { yellow.webinterface.hidePanesOnKeydown(yellow.toolbox.getEventKeycode(e)); },
@@ -112,8 +112,8 @@ yellow.webinterface =
 				"<h1>"+this.getText("LoginText")+"</h1>"+
 				"<h1>"+this.getText("LoginText")+"</h1>"+
 				"<form method=\"post\">"+
 				"<form method=\"post\">"+
 				"<input type=\"hidden\" name=\"action\" value=\"login\" />"+
 				"<input type=\"hidden\" name=\"action\" value=\"login\" />"+
-				"<p><label for=\"email\">"+this.getText("LoginEmail")+"</label> <input name=\"email\" id=\"email\" maxlength=\"64\" value=\""+yellow.config.webinterfaceDefaultEmail+"\" /></p>"+
-				"<p><label for=\"password\">"+this.getText("LoginPassword")+"</label> <input type=\"password\" name=\"password\" id=\"password\" maxlength=\"64\" value=\""+yellow.config.webinterfaceDefaultPassword+"\" /></p>"+
+				"<p><label for=\"email\">"+this.getText("LoginEmail")+"</label> <input name=\"email\" id=\"email\" maxlength=\"64\" value=\""+yellow.config.webinterfaceEmail+"\" /></p>"+
+				"<p><label for=\"password\">"+this.getText("LoginPassword")+"</label> <input type=\"password\" name=\"password\" id=\"password\" maxlength=\"64\" value=\""+yellow.config.webinterfacePassword+"\" /></p>"+
 				"<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("LoginButton")+"\" /></p>"+
 				"<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("LoginButton")+"\" /></p>"+
 				"</form>";
 				"</form>";
 		} else if(paneId == "yellow-pane-edit") {
 		} else if(paneId == "yellow-pane-edit") {

+ 17 - 13
system/core/core-webinterface.php

@@ -5,7 +5,7 @@
 // Web interface core plugin
 // Web interface core plugin
 class YellowWebinterface
 class YellowWebinterface
 {
 {
-	const Version = "0.3.7";
+	const Version = "0.4.1";
 	var $yellow;				//access to API
 	var $yellow;				//access to API
 	var $users;					//web interface users
 	var $users;					//web interface users
 	var $active;				//web interface is active? (boolean)
 	var $active;				//web interface is active? (boolean)
@@ -24,8 +24,8 @@ class YellowWebinterface
 		$this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
 		$this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
 		$this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
 		$this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
 		$this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
 		$this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
-		$this->yellow->config->setDefault("webinterfaceDefaultEmail", "");
-		$this->yellow->config->setDefault("webinterfaceDefaultPassword", "");
+		$this->yellow->config->setDefault("webinterfaceEmail", "");
+		$this->yellow->config->setDefault("webinterfacePassword", "");
 		$this->yellow->config->setDefault("webinterfaceNewPage", "default");
 		$this->yellow->config->setDefault("webinterfaceNewPage", "default");
 		$this->yellow->config->setDefault("webinterfaceFilePrefix", "published");
 		$this->yellow->config->setDefault("webinterfaceFilePrefix", "published");
 		$this->users = new YellowWebinterfaceUsers($yellow);
 		$this->users = new YellowWebinterfaceUsers($yellow);
@@ -435,8 +435,9 @@ class YellowWebinterface
 		$page->fileName = $this->yellow->toolbox->findFileFromTitle(
 		$page->fileName = $this->yellow->toolbox->findFileFromTitle(
 			$page->get($this->yellow->config->get("webinterfaceFilePrefix")), $page->get("title"), $fileName,
 			$page->get($this->yellow->config->get("webinterfaceFilePrefix")), $page->get("title"), $fileName,
 			$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 			$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
-		$page->location = $this->yellow->toolbox->findLocationFromFile($page->fileName,
-			$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentHomeDir"),
+		$page->location = $this->yellow->toolbox->findLocationFromFile(
+			$page->fileName, $this->yellow->config->get("contentDir"),
+			$this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
 			$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 			$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 		if($this->yellow->pages->find($page->location))
 		if($this->yellow->pages->find($page->location))
 		{
 		{
@@ -450,8 +451,9 @@ class YellowWebinterface
 				$page->fileName = $this->yellow->toolbox->findFileFromTitle(
 				$page->fileName = $this->yellow->toolbox->findFileFromTitle(
 					$page->get($this->yellow->config->get("webinterfaceFilePrefix")), $titleText.$titleNumber, $fileName,
 					$page->get($this->yellow->config->get("webinterfaceFilePrefix")), $titleText.$titleNumber, $fileName,
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
-				$page->location = $this->yellow->toolbox->findLocationFromFile($page->fileName,
-					$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentHomeDir"),
+				$page->location = $this->yellow->toolbox->findLocationFromFile(
+					$page->fileName, $this->yellow->config->get("contentDir"),
+					$this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 				if(!$this->yellow->pages->find($page->location)) { $ok = true; break; }
 				if(!$this->yellow->pages->find($page->location)) { $ok = true; break; }
 			}
 			}
@@ -477,8 +479,9 @@ class YellowWebinterface
 				$page->fileName = $this->yellow->toolbox->findFileFromTitle(
 				$page->fileName = $this->yellow->toolbox->findFileFromTitle(
 					$page->get($prefix), $page->get("title"), $fileName,
 					$page->get($prefix), $page->get("title"), $fileName,
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
-				$page->location = $this->yellow->toolbox->findLocationFromFile($page->fileName,
-					$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentHomeDir"),
+				$page->location = $this->yellow->toolbox->findLocationFromFile(
+					$page->fileName, $this->yellow->config->get("contentDir"),
+					$this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 				if($pageSource->location!=$page->location && $this->yellow->pages->find($page->location))
 				if($pageSource->location!=$page->location && $this->yellow->pages->find($page->location))
 				{
 				{
@@ -493,8 +496,9 @@ class YellowWebinterface
 	// Return content data for new page
 	// Return content data for new page
 	function getDataNew($title = "")
 	function getDataNew($title = "")
 	{
 	{
-		$fileName = $this->yellow->toolbox->findFileFromLocation($this->yellow->page->location,
-			$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentHomeDir"),
+		$fileName = $this->yellow->toolbox->findFileFromLocation(
+			$this->yellow->page->location, $this->yellow->config->get("contentDir"),
+			$this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
 			$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 			$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
 		$fileName = $this->yellow->toolbox->findNameFromFile($fileName,
 		$fileName = $this->yellow->toolbox->findNameFromFile($fileName,
 			$this->yellow->config->get("configDir"), $this->yellow->config->get("webinterfaceNewPage"),
 			$this->yellow->config->get("configDir"), $this->yellow->config->get("webinterfaceNewPage"),
@@ -523,8 +527,8 @@ class YellowWebinterface
 			$data["serverName"] = $this->yellow->config->get("serverName");
 			$data["serverName"] = $this->yellow->config->get("serverName");
 			$data["serverBase"] = $this->yellow->config->get("serverBase");
 			$data["serverBase"] = $this->yellow->config->get("serverBase");
 		} else {
 		} else {
-			$data["webinterfaceDefaultEmail"] = $this->yellow->config->get("webinterfaceDefaultEmail");
-			$data["webinterfaceDefaultPassword"] = $this->yellow->config->get("webinterfaceDefaultPassword");
+			$data["webinterfaceEmail"] = $this->yellow->config->get("webinterfaceEmail");
+			$data["webinterfacePassword"] = $this->yellow->config->get("webinterfacePassword");
 		}
 		}
 		return $data;
 		return $data;
 	}
 	}

+ 299 - 114
system/core/core.php

@@ -5,7 +5,7 @@
 // Yellow main class
 // Yellow main class
 class Yellow
 class Yellow
 {
 {
-	const Version = "0.3.19";
+	const Version = "0.4.1";
 	var $page;				//current page
 	var $page;				//current page
 	var $pages;				//pages from file system
 	var $pages;				//pages from file system
 	var $config;			//configuration
 	var $config;			//configuration
@@ -22,9 +22,9 @@ class Yellow
 		$this->plugins = new YellowPlugins();
 		$this->plugins = new YellowPlugins();
 		$this->config->setDefault("sitename", "Yellow");
 		$this->config->setDefault("sitename", "Yellow");
 		$this->config->setDefault("author", "Yellow");
 		$this->config->setDefault("author", "Yellow");
-		$this->config->setDefault("language", "en");
 		$this->config->setDefault("style", "default");
 		$this->config->setDefault("style", "default");
 		$this->config->setDefault("template", "default");
 		$this->config->setDefault("template", "default");
+		$this->config->setDefault("language", "en");
 		$this->config->setDefault("serverScheme", $this->toolbox->getServerScheme());
 		$this->config->setDefault("serverScheme", $this->toolbox->getServerScheme());
 		$this->config->setDefault("serverName", $this->toolbox->getServerName());
 		$this->config->setDefault("serverName", $this->toolbox->getServerName());
 		$this->config->setDefault("serverBase", $this->toolbox->getServerBase());
 		$this->config->setDefault("serverBase", $this->toolbox->getServerBase());
@@ -40,6 +40,7 @@ class Yellow
 		$this->config->setDefault("styleDir", "media/styles/");
 		$this->config->setDefault("styleDir", "media/styles/");
 		$this->config->setDefault("imageDir", "media/images/");
 		$this->config->setDefault("imageDir", "media/images/");
 		$this->config->setDefault("contentDir", "content/");
 		$this->config->setDefault("contentDir", "content/");
+		$this->config->setDefault("contentRootDir", "default/");
 		$this->config->setDefault("contentHomeDir", "home/");
 		$this->config->setDefault("contentHomeDir", "home/");
 		$this->config->setDefault("contentDefaultFile", "page.txt");
 		$this->config->setDefault("contentDefaultFile", "page.txt");
 		$this->config->setDefault("contentPagination", "page");
 		$this->config->setDefault("contentPagination", "page");
@@ -50,6 +51,7 @@ class Yellow
 		$this->config->setDefault("textStringFile", "text(.*).ini");
 		$this->config->setDefault("textStringFile", "text(.*).ini");
 		$this->config->setDefault("parser", "markdownextra");
 		$this->config->setDefault("parser", "markdownextra");
 		$this->config->setDefault("parserSafeMode", "0");
 		$this->config->setDefault("parserSafeMode", "0");
+		$this->config->setDefault("multiLanguageMode", "0");
 		$this->config->load($this->config->get("configDir").$this->config->get("configFile"));
 		$this->config->load($this->config->get("configDir").$this->config->get("configFile"));
 		$this->text->load($this->config->get("configDir").$this->config->get("textStringFile"));
 		$this->text->load($this->config->get("configDir").$this->config->get("textStringFile"));
 		$this->updateConfig();
 		$this->updateConfig();
@@ -222,21 +224,6 @@ class Yellow
 		}
 		}
 	}
 	}
 
 
-	// Update dynamic configuration
-	function updateConfig()
-	{
-		if(!$this->isContentDirectory("/"))
-		{
-			$path = $this->config->get("contentDir");
-			foreach($this->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
-			{
-				$name = $this->toolbox->normaliseName($entry);
-				$this->config->set("contentHomeDir", $name."/");
-				break;
-			}
-		}
-	}
-	
 	// Return request information
 	// Return request information
 	function getRequestInformation($serverScheme = "", $serverName = "", $base = "")
 	function getRequestInformation($serverScheme = "", $serverName = "", $base = "")
 	{
 	{
@@ -245,8 +232,8 @@ class Yellow
 		$base = empty($base) ? $this->config->get("serverBase") : $base;
 		$base = empty($base) ? $this->config->get("serverBase") : $base;
 		$location = $this->toolbox->getLocationClean();
 		$location = $this->toolbox->getLocationClean();
 		$location = substru($location, strlenu($base));
 		$location = substru($location, strlenu($base));
-		$fileName = $this->toolbox->findFileFromLocation($location,
-			$this->config->get("contentDir"), $this->config->get("contentHomeDir"),
+		$fileName = $this->toolbox->findFileFromLocation($location, $this->config->get("contentDir"),
+			$this->config->get("contentRootDir"), $this->config->get("contentHomeDir"),
 			$this->config->get("contentDefaultFile"), $this->config->get("contentExtension"));
 			$this->config->get("contentDefaultFile"), $this->config->get("contentExtension"));
 		return array($serverScheme, $serverName, $base, $location, $fileName);
 		return array($serverScheme, $serverName, $base, $location, $fileName);
 	}
 	}
@@ -260,11 +247,21 @@ class Yellow
 	// Check if content directory exists
 	// Check if content directory exists
 	function isContentDirectory($location)
 	function isContentDirectory($location)
 	{
 	{
-		$path = $this->toolbox->findFileFromLocation($location,
-			$this->config->get("contentDir"), $this->config->get("contentHomeDir"), "", "");
+		$path = $this->toolbox->findFileFromLocation($location, $this->config->get("contentDir"),
+			$this->config->get("contentRootDir"), $this->config->get("contentHomeDir"), "", "");
 		return is_dir($path);
 		return is_dir($path);
 	}
 	}
 	
 	
+	// Update content configuration
+	function updateConfig()
+	{
+		list($pathRoot, $pathHome) = $this->toolbox->findRootConfig($this->config->get("contentDir"),
+			$this->config->get("contentRootDir"), $this->config->get("contentHomeDir"),
+			$this->config->get("multiLanguageMode"));
+		$this->config->set("contentRootDir", $pathRoot);
+		$this->config->set("contentHomeDir", $pathHome);
+	}
+	
 	// Execute command
 	// Execute command
 	function command($name, $args = NULL)
 	function command($name, $args = NULL)
 	{
 	{
@@ -293,7 +290,7 @@ class Yellow
 		}
 		}
 	}
 	}
 	
 	
-	// Execute snippet code
+	// Execute snippet
 	function snippet($name, $args = NULL)
 	function snippet($name, $args = NULL)
 	{
 	{
 		$this->pages->snippetArgs = func_get_args();
 		$this->pages->snippetArgs = func_get_args();
@@ -387,11 +384,13 @@ class YellowPage
 		$this->set("title", $this->yellow->toolbox->createTextTitle($this->location));
 		$this->set("title", $this->yellow->toolbox->createTextTitle($this->location));
 		$this->set("sitename", $this->yellow->config->get("sitename"));
 		$this->set("sitename", $this->yellow->config->get("sitename"));
 		$this->set("author", $this->yellow->config->get("author"));
 		$this->set("author", $this->yellow->config->get("author"));
-		$this->set("language", $this->yellow->config->get("language"));
 		$this->set("style", $this->yellow->toolbox->findNameFromFile($this->fileName,
 		$this->set("style", $this->yellow->toolbox->findNameFromFile($this->fileName,
 			$this->yellow->config->get("styleDir"), $this->yellow->config->get("style"), ".css"));
 			$this->yellow->config->get("styleDir"), $this->yellow->config->get("style"), ".css"));
 		$this->set("template", $this->yellow->toolbox->findNameFromFile($this->fileName,
 		$this->set("template", $this->yellow->toolbox->findNameFromFile($this->fileName,
 			$this->yellow->config->get("templateDir"), $this->yellow->config->get("template"), ".php"));
 			$this->yellow->config->get("templateDir"), $this->yellow->config->get("template"), ".php"));
+		$this->set("language", $this->yellow->toolbox->findLanguageFromFile($this->fileName,
+			$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentRootDir"),
+			$this->yellow->config->get("language")));
 		$this->set("parser", $this->yellow->config->get("parser"));
 		$this->set("parser", $this->yellow->config->get("parser"));
 		
 		
 		if(preg_match("/^(\-\-\-[\r\n]+)(.+?)([\r\n]+\-\-\-[\r\n]+)/s", $this->rawData, $parsed))
 		if(preg_match("/^(\-\-\-[\r\n]+)(.+?)([\r\n]+\-\-\-[\r\n]+)/s", $this->rawData, $parsed))
@@ -407,7 +406,8 @@ class YellowPage
 			$this->set("title", $parsed[1]);
 			$this->set("title", $parsed[1]);
 		}
 		}
 		
 		
-		$titleHeader = $this->location!="/" ? $this->get("title")." - ".$this->get("sitename") : $this->get("sitename");
+		$shortHeader = $this->location == $this->yellow->pages->getHomeLocation($this->location);
+		$titleHeader = $shortHeader ? $this->get("sitename") : $this->get("title")." - ".$this->get("sitename");
 		if(!$this->isExisting("titleHeader")) $this->set("titleHeader", $titleHeader);
 		if(!$this->isExisting("titleHeader")) $this->set("titleHeader", $titleHeader);
 		if(!$this->isExisting("titleNavigation")) $this->set("titleNavigation", $this->get("title"));
 		if(!$this->isExisting("titleNavigation")) $this->set("titleNavigation", $this->get("title"));
 		if(!$this->isExisting("titleContent")) $this->set("titleContent", $this->get("title"));
 		if(!$this->isExisting("titleContent")) $this->set("titleContent", $this->get("title"));
@@ -427,7 +427,7 @@ class YellowPage
 		}
 		}
 	}
 	}
 	
 	
-	// Parse page content
+	// Parse page content on demand
 	function parseContent()
 	function parseContent()
 	{
 	{
 		if(!is_object($this->parser))
 		if(!is_object($this->parser))
@@ -473,7 +473,7 @@ class YellowPage
 				if(!is_null($output)) break;
 				if(!is_null($output)) break;
 			}
 			}
 		}
 		}
-		if(defined("DEBUG") && DEBUG>=2 && !empty($name)) echo "YellowPage::parseType name:$name shortcut:$typeShortcut<br/>\n";
+		if(defined("DEBUG") && DEBUG>=3 && !empty($name)) echo "YellowPage::parseType name:$name shortcut:$typeShortcut<br/>\n";
 		return $output;
 		return $output;
 	}
 	}
 	
 	
@@ -532,18 +532,21 @@ class YellowPage
 		return $header;
 		return $header;
 	}
 	}
 	
 	
-	// Return parent page relative to current page
+	// Return parent page relative to current page, NULL if none
 	function getParent()
 	function getParent()
 	{
 	{
 		$parentLocation = $this->yellow->pages->getParentLocation($this->location);
 		$parentLocation = $this->yellow->pages->getParentLocation($this->location);
 		return $this->yellow->pages->find($parentLocation);
 		return $this->yellow->pages->find($parentLocation);
 	}
 	}
 	
 	
-	// Return top-level parent page of current page
-	function getParentTop($homeFailback = false)
+	// Return top-level page for current page, NULL if none
+	function getParentTop($homeFailback = true)
 	{
 	{
 		$parentTopLocation = $this->yellow->pages->getParentTopLocation($this->location);
 		$parentTopLocation = $this->yellow->pages->getParentTopLocation($this->location);
-		if($homeFailback && !$this->yellow->isContentDirectory($parentTopLocation)) $parentTopLocation = "/";
+		if(!$this->yellow->pages->find($parentTopLocation) && $homeFailback)
+		{
+			$parentTopLocation = $this->yellow->pages->getHomeLocation($this->location);
+		}
 		return $this->yellow->pages->find($parentTopLocation);
 		return $this->yellow->pages->find($parentTopLocation);
 	}
 	}
 	
 	
@@ -855,38 +858,42 @@ class YellowPages
 		$this->pages = array();
 		$this->pages = array();
 	}
 	}
 	
 	
-	// Return one page from file system
-	function find($location, $absoluteLocation = false)
-	{
-		if($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
-		$parentLocation = $this->getParentLocation($location);
-		$this->scanChildren($parentLocation);
-		foreach($this->pages[$parentLocation] as $page) if($page->location == $location) { $found = true; break; }
-		return $found ? $page : NULL;
-	}
-	
 	// Return page collection with all pages from file system
 	// Return page collection with all pages from file system
-	function index($showInvisible = false, $levelMax = 0)
+	function index($showInvisible = false, $showLanguages = false, $levelMax = 0)
 	{
 	{
-		return $this->findChildrenRecursive("", $showInvisible, $levelMax);
+		$rootLocation = $showLanguages ? "" : $this->getRootLocation($this->yellow->page->location);
+		return $this->findChildrenRecursive($rootLocation, $showInvisible, $levelMax);
 	}
 	}
 	
 	
 	// Return page collection with top-level navigation
 	// Return page collection with top-level navigation
-	function top($showInvisible = false)
+	function top($showInvisible = false, $showLanguages = false)
 	{
 	{
-		return $this->findChildren("", $showInvisible);
+		$rootLocation = $showLanguages ? "" : $this->getRootLocation($this->yellow->page->location);
+		$pages = $this->findChildren($rootLocation, $showInvisible);
+		if($showLanguages)
+		{
+			$this->scanChildren($rootLocation);
+			foreach($this->pages[$rootLocation] as $page)
+			{
+				if($home = $this->find(substru($page->location, 4)))
+				{
+					if($home->isVisible() || $showInvisible) $pages->append($home);
+				}
+			}
+		}
+		return $pages;
 	}
 	}
 	
 	
 	// Return page collection with path ancestry
 	// Return page collection with path ancestry
 	function path($location, $absoluteLocation = false)
 	function path($location, $absoluteLocation = false)
 	{
 	{
-		if($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
 		$pages = new YellowPageCollection($this->yellow);
 		$pages = new YellowPageCollection($this->yellow);
-		if($page = $this->find($location))
+		if($page = $this->find($location, $absoluteLocation))
 		{
 		{
 			$pages->prepend($page);
 			$pages->prepend($page);
 			for(; $parent = $page->getParent(); $page=$parent) $pages->prepend($parent);
 			for(; $parent = $page->getParent(); $page=$parent) $pages->prepend($parent);
-			if($page->location!="/" && $home=$this->find("/")) $pages->prepend($home);
+			$home = $this->find($this->getHomeLocation($page->location));
+			if($home && $home->location!=$page->location) $pages->prepend($home);
 		}
 		}
 		return $pages;
 		return $pages;
 	}
 	}
@@ -897,12 +904,31 @@ class YellowPages
 		return new YellowPageCollection($this->yellow);
 		return new YellowPageCollection($this->yellow);
 	}
 	}
 	
 	
+	// Return one page from file system, NULL if not found
+	function find($location, $absoluteLocation = false)
+	{
+		if(!$this->yellow->toolbox->isRootLocation($location))
+		{
+			if($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
+			$parentLocation = $this->getParentLocation($location);
+			$this->scanChildren($parentLocation);
+			foreach($this->pages[$parentLocation] as $page) if($page->location == $location) { $found = true; break; }
+		}
+		return $found ? $page : NULL;
+	}
+	
 	// Find child pages
 	// Find child pages
 	function findChildren($location, $showInvisible = false)
 	function findChildren($location, $showInvisible = false)
 	{
 	{
-		$this->scanChildren($location);
 		$pages = new YellowPageCollection($this->yellow);
 		$pages = new YellowPageCollection($this->yellow);
-		foreach($this->pages[$location] as $page) if($page->isVisible() || $showInvisible) $pages->append($page);
+		$this->scanChildren($location);
+		foreach($this->pages[$location] as $page)
+		{
+			if($page->isVisible() || $showInvisible)
+			{
+				if(!$this->yellow->toolbox->isRootLocation($page->location)) $pages->append($page);
+			}
+		}
 		return $pages;
 		return $pages;
 	}
 	}
 	
 	
@@ -910,13 +936,13 @@ class YellowPages
 	function findChildrenRecursive($location, $showInvisible = false, $levelMax = 0)
 	function findChildrenRecursive($location, $showInvisible = false, $levelMax = 0)
 	{
 	{
 		--$levelMax;
 		--$levelMax;
-		$this->scanChildren($location);
 		$pages = new YellowPageCollection($this->yellow);
 		$pages = new YellowPageCollection($this->yellow);
+		$this->scanChildren($location);
 		foreach($this->pages[$location] as $page)
 		foreach($this->pages[$location] as $page)
 		{
 		{
 			if($page->isVisible() || $showInvisible)
 			if($page->isVisible() || $showInvisible)
 			{
 			{
-				$pages->append($page);
+				if(!$this->yellow->toolbox->isRootLocation($page->location)) $pages->append($page);
 				if(!$this->yellow->toolbox->isFileLocation($page->location) && $levelMax!=0)
 				if(!$this->yellow->toolbox->isFileLocation($page->location) && $levelMax!=0)
 				{
 				{
 					$pages->merge($this->findChildrenRecursive($page->location, $showInvisible, $levelMax));
 					$pages->merge($this->findChildrenRecursive($page->location, $showInvisible, $levelMax));
@@ -933,49 +959,87 @@ class YellowPages
 		{
 		{
 			if(defined("DEBUG") && DEBUG>=2) echo "YellowPages::scanChildren location:$location<br/>\n";
 			if(defined("DEBUG") && DEBUG>=2) echo "YellowPages::scanChildren location:$location<br/>\n";
 			$this->pages[$location] = array();
 			$this->pages[$location] = array();
-			$fileNames = $this->yellow->toolbox->findChildrenFromLocation($location,
-				$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentHomeDir"),
-				$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
-			foreach($fileNames as $fileName)
+			if(empty($location))
 			{
 			{
-				$fileHandle = @fopen($fileName, "r");
-				if($fileHandle)
+				$rootLocations = $this->yellow->toolbox->findRootLocations($this->yellow->config->get("contentDir"),
+					$this->yellow->config->get("contentRootDir"));
+				foreach($rootLocations as $rootLocation)
 				{
 				{
-					$fileData = fread($fileHandle, 4096);
-					$statusCode = filesize($fileName) <= 4096 ? 200 : 0;
-					fclose($fileHandle);
-				} else {
-					$fileData = "";
-					$statusCode = 0;
+					$page = new YellowPage($this->yellow,
+						$this->yellow->page->serverScheme, $this->yellow->page->serverName, $this->yellow->page->base,
+						$rootLocation, "");
+					$page->parseData("", false, 0);
+					array_push($this->pages[$location], $page);
+				}
+			} else {
+				$fileNames = $this->yellow->toolbox->findChildrenFromLocation($location, $this->yellow->config->get("contentDir"),
+					$this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
+					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
+				foreach($fileNames as $fileName)
+				{
+					$fileHandle = @fopen($fileName, "r");
+					if($fileHandle)
+					{
+						$fileData = fread($fileHandle, 4096);
+						$statusCode = filesize($fileName) <= 4096 ? 200 : 0;
+						fclose($fileHandle);
+					} else {
+						$fileData = "";
+						$statusCode = 0;
+					}
+					$page = new YellowPage($this->yellow,
+						$this->yellow->page->serverScheme, $this->yellow->page->serverName, $this->yellow->page->base,
+						$this->yellow->toolbox->findLocationFromFile($fileName, $this->yellow->config->get("contentDir"),
+						$this->yellow->config->get("contentRootDir"), $this->yellow->config->get("contentHomeDir"),
+						$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension")),
+						$fileName);
+					$page->parseData($fileData, false, $statusCode);
+					array_push($this->pages[$location], $page);
 				}
 				}
-				$page = new YellowPage($this->yellow,
-					$this->yellow->page->serverScheme, $this->yellow->page->serverName, $this->yellow->page->base,
-					$this->yellow->toolbox->findLocationFromFile($fileName,
-					$this->yellow->config->get("contentDir"), $this->yellow->config->get("contentHomeDir"),
-					$this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension")),
-					$fileName);
-				$page->parseData($fileData, false, $statusCode);
-				array_push($this->pages[$location], $page);
 			}
 			}
 		}
 		}
 	}
 	}
 	
 	
+	// Return root location
+	function getRootLocation($location)
+	{
+		$rootLocation = "root/";
+		if($this->yellow->config->get("multiLanguageMode"))
+		{
+			$this->scanChildren("");
+			foreach($this->pages[""] as $page)
+			{
+				$token = substru($page->location, 4);
+				if($token!="/" && substru($location, 0, strlenu($token))==$token) { $rootLocation = "root$token"; break; }
+			}
+		}
+		return $rootLocation;
+	}
+
+	// Return home location
+	function getHomeLocation($location)
+	{
+		return substru($this->getRootLocation($location), 4);
+	}
+	
 	// Return parent location
 	// Return parent location
 	function getParentLocation($location)
 	function getParentLocation($location)
 	{
 	{
-		$parentLocation = "";
-		if(preg_match("/^(.*\/).+?$/", $location, $matches))
+		$prefix = rtrim(substru($this->getRootLocation($location), 4), '/');
+		if(preg_match("#^($prefix.*\/).+?$#", $location, $matches))
 		{
 		{
-			if($matches[1]!="/" || $this->yellow->toolbox->isFileLocation($location)) $parentLocation = $matches[1];
+			if($matches[1]!="$prefix/" || $this->yellow->toolbox->isFileLocation($location)) $parentLocation = $matches[1];
 		}
 		}
+		if(empty($parentLocation)) $parentLocation = "root$prefix/";
 		return $parentLocation;
 		return $parentLocation;
 	}
 	}
 	
 	
-	// Return top-level parent location
+	// Return top-level location
 	function getParentTopLocation($location)
 	function getParentTopLocation($location)
 	{
 	{
-		$parentTopLocation = "/";
-		if(preg_match("/^(.+?\/)/", $location, $matches)) $parentTopLocation = $matches[1];
+		$prefix = rtrim(substru($this->getRootLocation($location), 4), '/');
+		if(preg_match("#^($prefix.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
+		if(empty($parentTopLocation)) $parentTopLocation = "$prefix/";
 		return $parentTopLocation;
 		return $parentTopLocation;
 	}
 	}
 }
 }
@@ -1334,6 +1398,12 @@ class YellowToolbox
 		return preg_match("/^(.*\/)?$pagination:\d*$/", $location);
 		return preg_match("/^(.*\/)?$pagination:\d*$/", $location);
 	}
 	}
 
 
+	// Check if location is specifying root
+	function isRootLocation($location)
+	{
+		return $location[0] != "/";
+	}
+
 	// Check if location is specifying file or directory
 	// Check if location is specifying file or directory
 	function isFileLocation($location)
 	function isFileLocation($location)
 	{
 	{
@@ -1413,14 +1483,65 @@ class YellowToolbox
 		return $visible;
 		return $visible;
 	}
 	}
 	
 	
+	// Return root configuration
+	function findRootConfig($pathBase, $pathRoot, $pathHome, $multiLanguageMode)
+	{
+		$path = $pathBase;
+		if(!$multiLanguageMode) $pathRoot = "";
+		if(!empty($pathRoot))
+		{
+			$token = $root = rtrim($pathRoot, '/');
+			foreach($this->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
+			{
+				if(empty($firstRoot)) { $firstRoot = $token = $entry; }
+				if($this->normaliseName($entry) == $root) { $token = $entry; break; }
+			}
+			$pathRoot = $this->normaliseName($token)."/";
+			$path .= "$firstRoot/";
+		}
+		if(!empty($pathHome))
+		{
+			$token = $home = rtrim($pathHome, '/');
+			foreach($this->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
+			{
+				if(empty($firstHome)) { $firstHome = $token = $entry; }
+				if($this->normaliseName($entry) == $home) { $token = $entry; break; }
+			}
+			$pathHome = $this->normaliseName($token)."/";
+		}
+		return array($pathRoot, $pathHome);
+	}
+	
+	// Return root locations
+	function findRootLocations($pathBase, $pathRoot)
+	{
+		$locations = array("root/");
+		if(!empty($pathRoot))
+		{
+			$root = rtrim($pathRoot, '/');
+			foreach($this->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry)
+			{
+				$token = $this->normaliseName($entry);
+				if($token != $root) array_push($locations, "root/$token/");
+			}
+		}
+		return $locations;
+	}
+	
 	// Return location from file path
 	// Return location from file path
-	function findLocationFromFile($fileName, $pathBase, $pathHome, $fileDefault, $fileExtension)
+	function findLocationFromFile($fileName, $pathBase, $pathRoot, $pathHome, $fileDefault, $fileExtension)
 	{
 	{
 		$location = "/";
 		$location = "/";
 		if(substru($fileName, 0, strlenu($pathBase)) == $pathBase)
 		if(substru($fileName, 0, strlenu($pathBase)) == $pathBase)
 		{
 		{
 			$fileName = substru($fileName, strlenu($pathBase));
 			$fileName = substru($fileName, strlenu($pathBase));
 			$tokens = explode('/', $fileName);
 			$tokens = explode('/', $fileName);
+			if(!empty($pathRoot))
+			{
+				$token = $this->normaliseName($tokens[0]).'/';
+				if($token!=$pathRoot) $location .= $token;
+				array_shift($tokens);
+			}
 			for($i=0; $i<count($tokens)-1; ++$i)
 			for($i=0; $i<count($tokens)-1; ++$i)
 			{
 			{
 				$token = $this->normaliseName($tokens[$i]).'/';
 				$token = $this->normaliseName($tokens[$i]).'/';
@@ -1434,20 +1555,24 @@ class YellowToolbox
 		} else {
 		} else {
 			$invalid = true;
 			$invalid = true;
 		}
 		}
+		if(defined("DEBUG") && DEBUG>=2)
+		{
+			$debug = ($invalid ? "INVALID" : $location)." <- $pathBase$fileName";
+			echo "YellowToolbox::findLocationFromFile $debug<br/>\n";
+		}
 		return $invalid ? "" : $location;
 		return $invalid ? "" : $location;
 	}
 	}
 	
 	
 	// Return file path from location
 	// Return file path from location
-	function findFileFromLocation($location, $pathBase, $pathHome, $fileDefault, $fileExtension)
+	function findFileFromLocation($location, $pathBase, $pathRoot, $pathHome, $fileDefault, $fileExtension)
 	{
 	{
 		$path = $pathBase;
 		$path = $pathBase;
-		$tokens = explode('/', $location);
-		if(count($tokens) > 2)
+		if($this->isRootLocation($location))
 		{
 		{
-			if($tokens[1]."/" == $pathHome) $invalid = true;
-			for($i=1; $i<count($tokens)-1; ++$i)
+			if(!empty($pathRoot))
 			{
 			{
-				$token = $tokens[$i];
+				$token = rtrim(substru($location, 5), '/');
+				if(empty($token)) $token = rtrim($pathRoot, '/');
 				if($this->normaliseName($token) != $token) $invalid = true;
 				if($this->normaliseName($token) != $token) $invalid = true;
 				$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
 				$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
 				foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
 				foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
@@ -1457,54 +1582,101 @@ class YellowToolbox
 				$path .= "$token/";
 				$path .= "$token/";
 			}
 			}
 		} else {
 		} else {
-			$i = 1;
-			$token = $tokens[0] = rtrim($pathHome, '/');
-			if($this->normaliseName($token) != $token) $invalid = true;
-			$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
-			foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
+			$tokens = explode('/', $location);
+			if(!empty($pathRoot))
 			{
 			{
-				if($this->normaliseName($entry) == $token) { $token = $entry; break; }
+				if(count($tokens) > 2)
+				{
+					$root = $tokens[1];
+					if($this->normaliseName($root) == $this->normaliseName($pathRoot)) $invalid = true;
+					if($this->normaliseName($root) != $root) $invalid = true;
+					$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $root)."$/";
+					foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
+					{
+						if($this->normaliseName($entry) == $root) { $token = $entry; array_shift($tokens); break; }
+					}
+				}
+				if(empty($token))
+				{
+					$token = rtrim($pathRoot, '/');
+					if($this->normaliseName($token) != $token) $invalid = true;
+					$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
+					foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
+					{
+						if($this->normaliseName($entry) == $token) { $token = $entry; break; }
+					}
+				}
+				$path .= "$token/";
 			}
 			}
-			$path .= "$token/";
-		}
-		if(!empty($fileDefault) && !empty($fileExtension))
-		{
-			if(!empty($tokens[$i]))
+			if(count($tokens) > 2)
 			{
 			{
-				$token = $tokens[$i].$fileExtension;
-				$fileFolder = $tokens[$i-1].$fileExtension;
-				if($token==$fileDefault || $token==$fileFolder) $invalid = true;
+				if($this->normaliseName($tokens[1]) == $this->normaliseName($pathHome)) $invalid = true;
+				for($i=1; $i<count($tokens)-1; ++$i)
+				{
+					$token = $tokens[$i];
+					if($this->normaliseName($token) != $token) $invalid = true;
+					$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
+					foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
+					{
+						if($this->normaliseName($entry) == $token) { $token = $entry; break; }
+					}
+					$path .= "$token/";
+				}
+			} else {
+				$i = 1;
+				$token = rtrim($pathHome, '/');
 				if($this->normaliseName($token) != $token) $invalid = true;
 				if($this->normaliseName($token) != $token) $invalid = true;
 				$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
 				$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
-				foreach($this->getDirectoryEntries($path, $regex, false, false, false) as $entry)
+				foreach($this->getDirectoryEntries($path, $regex, false, true, false) as $entry)
 				{
 				{
 					if($this->normaliseName($entry) == $token) { $token = $entry; break; }
 					if($this->normaliseName($entry) == $token) { $token = $entry; break; }
 				}
 				}
-			} else {
-				$token = $fileDefault;
-				if(!is_file($path."/".$fileDefault))
+				$path .= "$token/";
+			}
+			if(!empty($fileDefault) && !empty($fileExtension))
+			{
+				if(!empty($tokens[$i]))
 				{
 				{
+					$token = $tokens[$i].$fileExtension;
 					$fileFolder = $tokens[$i-1].$fileExtension;
 					$fileFolder = $tokens[$i-1].$fileExtension;
-					$regex = "/^[\d\-\_\.]*($fileDefault|$fileFolder)$/";
-					foreach($this->getDirectoryEntries($path, $regex, true, false, false) as $entry)
+					if($token==$fileDefault || $token==$fileFolder) $invalid = true;
+					if($this->normaliseName($token) != $token) $invalid = true;
+					$regex = $invalid ? "//" : "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
+					foreach($this->getDirectoryEntries($path, $regex, false, false, false) as $entry)
+					{
+						if($this->normaliseName($entry) == $token) { $token = $entry; break; }
+					}
+				} else {
+					$token = $fileDefault;
+					if(!is_file($path."/".$fileDefault))
 					{
 					{
-						if($this->normaliseName($entry) == $fileDefault) { $token = $entry; break; }
-						if($this->normaliseName($entry) == $fileFolder) { $token = $entry; break; }
+						$fileFolder = $tokens[$i-1].$fileExtension;
+						$regex = "/^[\d\-\_\.]*($fileDefault|$fileFolder)$/";
+						foreach($this->getDirectoryEntries($path, $regex, true, false, false) as $entry)
+						{
+							if($this->normaliseName($entry) == $fileDefault) { $token = $entry; break; }
+							if($this->normaliseName($entry) == $fileFolder) { $token = $entry; break; }
+						}
 					}
 					}
 				}
 				}
+				$path .= $token;
+				if(defined("DEBUG") && DEBUG>=2)
+				{
+					$debug = "$location -> ".($invalid ? "INVALID" : $path);
+					echo "YellowToolbox::findFileFromLocation $debug<br/>\n";
+				}
 			}
 			}
-			$path .= $token;
 		}
 		}
 		return $invalid ? "" : $path;
 		return $invalid ? "" : $path;
 	}
 	}
 	
 	
-	// Return file path of children from location
-	function findChildrenFromLocation($location, $pathBase, $pathHome, $fileDefault, $fileExtension)
+	// Return children from location
+	function findChildrenFromLocation($location, $pathBase, $pathRoot, $pathHome, $fileDefault, $fileExtension)
 	{
 	{
 		$fileNames = array();
 		$fileNames = array();
-		if(empty($location) || !$this->isFileLocation($location))
+		if(!$this->isFileLocation($location))
 		{
 		{
-			$path = empty($location) ? $pathBase : $this->findFileFromLocation($location, $pathBase, $pathHome, "", "");
+			$path = $this->findFileFromLocation($location, $pathBase, $pathRoot, $pathHome, "", "");
 			foreach($this->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
 			foreach($this->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
 			{
 			{
 				$token = $fileDefault;
 				$token = $fileDefault;
@@ -1520,7 +1692,7 @@ class YellowToolbox
 				}
 				}
 				array_push($fileNames, $path.$entry."/".$token);
 				array_push($fileNames, $path.$entry."/".$token);
 			}
 			}
-			if(!empty($location))
+			if(!$this->isRootLocation($location))
 			{
 			{
 				$fileFolder = $this->normaliseName(basename($path)).$fileExtension;
 				$fileFolder = $this->normaliseName(basename($path)).$fileExtension;
 				$regex = "/^.*\\".$fileExtension."$/";
 				$regex = "/^.*\\".$fileExtension."$/";
@@ -1544,6 +1716,19 @@ class YellowToolbox
 		if(!is_file("$pathBase$name$fileExtension")) $name = $this->normaliseName($nameDefault);
 		if(!is_file("$pathBase$name$fileExtension")) $name = $this->normaliseName($nameDefault);
 		return $includeFileName ? "$pathBase$name$fileExtension" : $name;
 		return $includeFileName ? "$pathBase$name$fileExtension" : $name;
 	}
 	}
+
+	// Return language from file path
+	function findLanguageFromFile($fileName, $pathBase, $pathRoot, $languageDefault)
+	{
+		$language = $languageDefault;
+		if(!empty($pathRoot))
+		{
+			$fileName = substru($fileName, strlenu($pathBase));
+			if(preg_match("/^(.+?)\//", $fileName, $matches)) $name = $this->normaliseName($matches[1]);
+			if(strlenu($name) == 2) $language = $name;
+		}
+		return $language;
+	}
 	
 	
 	// Return file path from title
 	// Return file path from title
 	function findFileFromTitle($titlePrefix, $titleText, $fileName, $fileDefault, $fileExtension)
 	function findFileFromTitle($titlePrefix, $titleText, $fileName, $fileDefault, $fileExtension)
@@ -1590,7 +1775,7 @@ class YellowToolbox
 		if($removeExtension) $text = ($pos = strrposu($text, '.')) ? substru($text, 0, $pos) : $text;
 		if($removeExtension) $text = ($pos = strrposu($text, '.')) ? substru($text, 0, $pos) : $text;
 		if($removePrefix) if(preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches)) $text = $matches[1];
 		if($removePrefix) if(preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches)) $text = $matches[1];
 		if($filterStrict) $text = strreplaceu('.', '-', strtoloweru($text));
 		if($filterStrict) $text = strreplaceu('.', '-', strtoloweru($text));
-		return preg_replace("/[^\pL\d\-\_\.]/u", "-", $text);
+		return preg_replace("/[^\pL\d\-\_\.]/u", "-", rtrim($text, '/'));
 	}
 	}
 		
 		
 	// Normalise text into UTF-8 NFC
 	// Normalise text into UTF-8 NFC
@@ -2000,7 +2185,7 @@ class YellowPlugins
 		foreach($this->plugins as $key=>$value)
 		foreach($this->plugins as $key=>$value)
 		{
 		{
 			$this->plugins[$key]["obj"] = new $value["class"];
 			$this->plugins[$key]["obj"] = new $value["class"];
-			if(defined("DEBUG") && DEBUG>=2) echo "YellowPlugins::load class:$value[class] $value[version]<br/>\n";
+			if(defined("DEBUG") && DEBUG>=3) echo "YellowPlugins::load class:$value[class] $value[version]<br/>\n";
 			if(method_exists($this->plugins[$key]["obj"], "onLoad")) $this->plugins[$key]["obj"]->onLoad($yellow);
 			if(method_exists($this->plugins[$key]["obj"], "onLoad")) $this->plugins[$key]["obj"]->onLoad($yellow);
 		}
 		}
 	}
 	}