Load multi-languages pages/sections
This commit is contained in:
parent
b0f6963e4c
commit
1d06324a65
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
target
|
target
|
||||||
.idea/
|
.idea/
|
||||||
test_site/public
|
test_site/public
|
||||||
|
test_site_i18n/public
|
||||||
docs/public
|
docs/public
|
||||||
|
|
||||||
small-blog
|
small-blog
|
||||||
|
|
|
@ -31,6 +31,8 @@ pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> {
|
||||||
pub struct FileInfo {
|
pub struct FileInfo {
|
||||||
/// The full path to the .md file
|
/// The full path to the .md file
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
/// The on-disk filename, will differ from the `name` when there is a language code in it
|
||||||
|
pub filename: String,
|
||||||
/// The name of the .md file without the extension, always `_index` for sections
|
/// The name of the .md file without the extension, always `_index` for sections
|
||||||
/// Doesn't contain the language if there was one in the filename
|
/// Doesn't contain the language if there was one in the filename
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -68,6 +70,7 @@ impl FileInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
FileInfo {
|
FileInfo {
|
||||||
|
filename: file_path.file_name().unwrap().to_string_lossy().to_string(),
|
||||||
path: file_path,
|
path: file_path,
|
||||||
// We don't care about grand parent for pages
|
// We don't care about grand parent for pages
|
||||||
grand_parent: None,
|
grand_parent: None,
|
||||||
|
@ -79,6 +82,7 @@ impl FileInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_section(path: &Path) -> FileInfo {
|
pub fn new_section(path: &Path) -> FileInfo {
|
||||||
|
let file_path = path.to_path_buf();
|
||||||
let parent = path.parent().unwrap().to_path_buf();
|
let parent = path.parent().unwrap().to_path_buf();
|
||||||
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
let components = find_content_components(path);
|
let components = find_content_components(path);
|
||||||
|
@ -90,7 +94,8 @@ impl FileInfo {
|
||||||
let grand_parent = parent.parent().map(|p| p.to_path_buf());
|
let grand_parent = parent.parent().map(|p| p.to_path_buf());
|
||||||
|
|
||||||
FileInfo {
|
FileInfo {
|
||||||
path: path.to_path_buf(),
|
filename: file_path.file_name().unwrap().to_string_lossy().to_string(),
|
||||||
|
path: file_path,
|
||||||
parent,
|
parent,
|
||||||
grand_parent,
|
grand_parent,
|
||||||
name,
|
name,
|
||||||
|
@ -136,6 +141,7 @@ impl Default for FileInfo {
|
||||||
path: PathBuf::new(),
|
path: PathBuf::new(),
|
||||||
parent: PathBuf::new(),
|
parent: PathBuf::new(),
|
||||||
grand_parent: None,
|
grand_parent: None,
|
||||||
|
filename: String::new(),
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
components: vec![],
|
components: vec![],
|
||||||
relative: String::new(),
|
relative: String::new(),
|
||||||
|
|
|
@ -80,14 +80,12 @@ impl Library {
|
||||||
/// Find out the direct subsections of each subsection if there are some
|
/// Find out the direct subsections of each subsection if there are some
|
||||||
/// as well as the pages for each section
|
/// as well as the pages for each section
|
||||||
pub fn populate_sections(&mut self) {
|
pub fn populate_sections(&mut self) {
|
||||||
let (root_path, index_path) = self
|
let root_path= self
|
||||||
.sections
|
.sections
|
||||||
.values()
|
.values()
|
||||||
.find(|s| s.is_index())
|
.find(|s| s.is_index())
|
||||||
.map(|s| (s.file.parent.clone(), s.file.path.clone()))
|
.map(|s| s.file.parent.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let root_key = self.paths_to_sections[&index_path];
|
|
||||||
|
|
||||||
// We are going to get both the ancestors and grandparents for each section in one go
|
// We are going to get both the ancestors and grandparents for each section in one go
|
||||||
let mut ancestors: HashMap<PathBuf, Vec<_>> = HashMap::new();
|
let mut ancestors: HashMap<PathBuf, Vec<_>> = HashMap::new();
|
||||||
let mut subsections: HashMap<PathBuf, Vec<_>> = HashMap::new();
|
let mut subsections: HashMap<PathBuf, Vec<_>> = HashMap::new();
|
||||||
|
@ -99,7 +97,8 @@ impl Library {
|
||||||
|
|
||||||
if let Some(ref grand_parent) = section.file.grand_parent {
|
if let Some(ref grand_parent) = section.file.grand_parent {
|
||||||
subsections
|
subsections
|
||||||
.entry(grand_parent.join("_index.md"))
|
// Using the original filename to work for multi-lingual sections
|
||||||
|
.entry(grand_parent.join(§ion.file.filename))
|
||||||
.or_insert_with(|| vec![])
|
.or_insert_with(|| vec![])
|
||||||
.push(section.file.path.clone());
|
.push(section.file.path.clone());
|
||||||
}
|
}
|
||||||
|
@ -111,6 +110,7 @@ impl Library {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut path = root_path.clone();
|
let mut path = root_path.clone();
|
||||||
|
let root_key = self.paths_to_sections[&root_path.join(§ion.file.filename)];
|
||||||
// Index section is the first ancestor of every single section
|
// Index section is the first ancestor of every single section
|
||||||
let mut parents = vec![root_key];
|
let mut parents = vec![root_key];
|
||||||
for component in §ion.file.components {
|
for component in §ion.file.components {
|
||||||
|
@ -119,7 +119,7 @@ impl Library {
|
||||||
if path == section.file.parent {
|
if path == section.file.parent {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(section_key) = self.paths_to_sections.get(&path.join("_index.md")) {
|
if let Some(section_key) = self.paths_to_sections.get(&path.join(§ion.file.filename)) {
|
||||||
parents.push(*section_key);
|
parents.push(*section_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,12 @@ impl Library {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, page) in &mut self.pages {
|
for (key, page) in &mut self.pages {
|
||||||
let mut parent_section_path = page.file.parent.join("_index.md");
|
let parent_filename = if let Some(ref lang) = page.lang {
|
||||||
|
format!("_index.{}.md", lang)
|
||||||
|
} else {
|
||||||
|
"_index.md".to_string()
|
||||||
|
};
|
||||||
|
let mut parent_section_path = page.file.parent.join(&parent_filename);
|
||||||
while let Some(section_key) = self.paths_to_sections.get(&parent_section_path) {
|
while let Some(section_key) = self.paths_to_sections.get(&parent_section_path) {
|
||||||
let parent_is_transparent;
|
let parent_is_transparent;
|
||||||
// We need to get a reference to a section later so keep the scope of borrowing small
|
// We need to get a reference to a section later so keep the scope of borrowing small
|
||||||
|
@ -158,9 +163,9 @@ impl Library {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've added `_index.md` so if we are here so we need to go up twice
|
// We've added `_index(.{LANG})?.md` so if we are here so we need to go up twice
|
||||||
match parent_section_path.clone().parent().unwrap().parent() {
|
match parent_section_path.clone().parent().unwrap().parent() {
|
||||||
Some(parent) => parent_section_path = parent.join("_index.md"),
|
Some(parent) => parent_section_path = parent.join(&parent_filename),
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,9 +147,14 @@ impl Site {
|
||||||
Ok(site)
|
Ok(site)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The index section is ALWAYS at that path
|
/// The index sections are ALWAYS at those paths
|
||||||
pub fn index_section_path(&self) -> PathBuf {
|
/// There are one index section for the basic language + 1 per language
|
||||||
self.content_path.join("_index.md")
|
fn index_section_paths(&self) -> Vec<(PathBuf, Option<String>)> {
|
||||||
|
let mut res = vec![(self.content_path.join("_index.md"), None)];
|
||||||
|
for language in &self.config.languages {
|
||||||
|
res.push((self.content_path.join(format!("_index.{}.md", language.code)), Some(language.code.clone())));
|
||||||
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We avoid the port the server is going to use as it's not bound yet
|
/// We avoid the port the server is going to use as it's not bound yet
|
||||||
|
@ -184,7 +189,7 @@ impl Site {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| !e.as_path().file_name().unwrap().to_str().unwrap().starts_with('.'))
|
.filter(|e| !e.as_path().file_name().unwrap().to_str().unwrap().starts_with('.'))
|
||||||
.partition(|entry| entry.as_path().file_name().unwrap() == "_index.md");
|
.partition(|entry| entry.as_path().file_name().unwrap().to_str().unwrap().starts_with("_index."));
|
||||||
|
|
||||||
self.library = Library::new(page_entries.len(), section_entries.len());
|
self.library = Library::new(page_entries.len(), section_entries.len());
|
||||||
|
|
||||||
|
@ -219,9 +224,9 @@ impl Site {
|
||||||
self.add_section(s, false)?;
|
self.add_section(s, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a default index section if necessary so we don't need to create
|
// Insert a default index section for each language if necessary so we don't need to create
|
||||||
// a _index.md to render the index page at the root of the site
|
// a _index.md to render the index page at the root of the site
|
||||||
let index_path = self.index_section_path();
|
for (index_path, lang) in self.index_section_paths() {
|
||||||
if let Some(ref index_section) = self.library.get_section(&index_path) {
|
if let Some(ref index_section) = self.library.get_section(&index_path) {
|
||||||
if self.config.build_search_index && !index_section.meta.in_search_index {
|
if self.config.build_search_index && !index_section.meta.in_search_index {
|
||||||
bail!(
|
bail!(
|
||||||
|
@ -234,19 +239,30 @@ impl Site {
|
||||||
// Not in else because of borrow checker
|
// Not in else because of borrow checker
|
||||||
if !self.library.contains_section(&index_path) {
|
if !self.library.contains_section(&index_path) {
|
||||||
let mut index_section = Section::default();
|
let mut index_section = Section::default();
|
||||||
|
index_section.file.parent = self.content_path.clone();
|
||||||
|
index_section.file.name = "_index".to_string();
|
||||||
|
index_section.file.filename = index_path.file_name().unwrap().to_string_lossy().to_string();
|
||||||
|
if let Some(ref l) = lang {
|
||||||
|
index_section.permalink = self.config.make_permalink(l);
|
||||||
|
let filename = format!("_index.{}.md", l);
|
||||||
|
index_section.file.path = self.content_path.join(&filename);
|
||||||
|
index_section.file.relative = filename;
|
||||||
|
index_section.lang = Some(l.clone());
|
||||||
|
} else {
|
||||||
index_section.permalink = self.config.make_permalink("");
|
index_section.permalink = self.config.make_permalink("");
|
||||||
index_section.file.path = self.content_path.join("_index.md");
|
index_section.file.path = self.content_path.join("_index.md");
|
||||||
index_section.file.parent = self.content_path.clone();
|
|
||||||
index_section.file.relative = "_index.md".to_string();
|
index_section.file.relative = "_index.md".to_string();
|
||||||
|
}
|
||||||
self.library.insert_section(index_section);
|
self.library.insert_section(index_section);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut pages_insert_anchors = HashMap::new();
|
let mut pages_insert_anchors = HashMap::new();
|
||||||
for page in pages {
|
for page in pages {
|
||||||
let p = page?;
|
let p = page?;
|
||||||
pages_insert_anchors.insert(
|
pages_insert_anchors.insert(
|
||||||
p.file.path.clone(),
|
p.file.path.clone(),
|
||||||
self.find_parent_section_insert_anchor(&p.file.parent.clone()),
|
self.find_parent_section_insert_anchor(&p.file.parent.clone(), &p.lang),
|
||||||
);
|
);
|
||||||
self.add_page(p, false)?;
|
self.add_page(p, false)?;
|
||||||
}
|
}
|
||||||
|
@ -274,7 +290,7 @@ impl Site {
|
||||||
for (_, p) in self.library.pages() {
|
for (_, p) in self.library.pages() {
|
||||||
pages_insert_anchors.insert(
|
pages_insert_anchors.insert(
|
||||||
p.file.path.clone(),
|
p.file.path.clone(),
|
||||||
self.find_parent_section_insert_anchor(&p.file.parent.clone()),
|
self.find_parent_section_insert_anchor(&p.file.parent.clone(), &p.lang),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +353,7 @@ impl Site {
|
||||||
pub fn add_page(&mut self, mut page: Page, render: bool) -> Result<Option<Page>> {
|
pub fn add_page(&mut self, mut page: Page, render: bool) -> Result<Option<Page>> {
|
||||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
||||||
if render {
|
if render {
|
||||||
let insert_anchor = self.find_parent_section_insert_anchor(&page.file.parent);
|
let insert_anchor = self.find_parent_section_insert_anchor(&page.file.parent, &page.lang);
|
||||||
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
|
page.render_markdown(&self.permalinks, &self.tera, &self.config, insert_anchor)?;
|
||||||
}
|
}
|
||||||
let prev = self.library.remove_page(&page.file.path);
|
let prev = self.library.remove_page(&page.file.path);
|
||||||
|
@ -363,8 +379,13 @@ impl Site {
|
||||||
|
|
||||||
/// Finds the insert_anchor for the parent section of the directory at `path`.
|
/// Finds the insert_anchor for the parent section of the directory at `path`.
|
||||||
/// Defaults to `AnchorInsert::None` if no parent section found
|
/// Defaults to `AnchorInsert::None` if no parent section found
|
||||||
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor {
|
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf, lang: &Option<String>) -> InsertAnchor {
|
||||||
match self.library.get_section(&parent_path.join("_index.md")) {
|
let parent = if let Some(ref l) = lang {
|
||||||
|
parent_path.join(format!("_index.{}.md", l))
|
||||||
|
} else {
|
||||||
|
parent_path.join("_index.md")
|
||||||
|
};
|
||||||
|
match self.library.get_section(&parent) {
|
||||||
Some(s) => s.meta.insert_anchor_links,
|
Some(s) => s.meta.insert_anchor_links,
|
||||||
None => InsertAnchor::None,
|
None => InsertAnchor::None,
|
||||||
}
|
}
|
||||||
|
|
46
components/site/tests/site_i18n.rs
Normal file
46
components/site/tests/site_i18n.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
extern crate site;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use site::Site;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_parse_multilingual_site() {
|
||||||
|
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
|
||||||
|
path.push("test_site_i18n");
|
||||||
|
let mut site = Site::new(&path, "config.toml").unwrap();
|
||||||
|
site.load().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(site.library.pages().len(), 9);
|
||||||
|
assert_eq!(site.library.sections().len(), 4);
|
||||||
|
|
||||||
|
// default index sections
|
||||||
|
let default_index_section = site.library.get_section(&path.join("content").join("_index.md")).unwrap();
|
||||||
|
assert_eq!(default_index_section.pages.len(), 1);
|
||||||
|
assert!(default_index_section.ancestors.is_empty());
|
||||||
|
|
||||||
|
let fr_index_section = site.library.get_section(&path.join("content").join("_index.fr.md")).unwrap();
|
||||||
|
assert_eq!(fr_index_section.pages.len(), 1);
|
||||||
|
assert!(fr_index_section.ancestors.is_empty());
|
||||||
|
|
||||||
|
// blog sections get only their own language pages
|
||||||
|
let blog_path = path.join("content").join("blog");
|
||||||
|
|
||||||
|
let default_blog = site.library.get_section(&blog_path.join("_index.md")).unwrap();
|
||||||
|
assert_eq!(default_blog.subsections.len(), 0);
|
||||||
|
assert_eq!(default_blog.pages.len(), 4);
|
||||||
|
assert_eq!(default_blog.ancestors, vec![*site.library.get_section_key(&default_index_section.file.path).unwrap()]);
|
||||||
|
for key in &default_blog.pages {
|
||||||
|
let page = site.library.get_page_by_key(*key);
|
||||||
|
assert_eq!(page.lang, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fr_blog = site.library.get_section(&blog_path.join("_index.fr.md")).unwrap();
|
||||||
|
assert_eq!(fr_blog.subsections.len(), 0);
|
||||||
|
assert_eq!(fr_blog.pages.len(), 3);
|
||||||
|
assert_eq!(fr_blog.ancestors, vec![*site.library.get_section_key(&fr_index_section.file.path).unwrap()]);
|
||||||
|
for key in &fr_blog.pages {
|
||||||
|
let page = site.library.get_page_by_key(*key);
|
||||||
|
assert_eq!(page.lang, Some("fr".to_string()));
|
||||||
|
}
|
||||||
|
}
|
19
test_site_i18n/config.toml
Normal file
19
test_site_i18n/config.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# The URL the site will be built for
|
||||||
|
base_url = "https://example.com"
|
||||||
|
|
||||||
|
# Whether to automatically compile all Sass files in the sass directory
|
||||||
|
compile_sass = false
|
||||||
|
|
||||||
|
# Whether to do syntax highlighting
|
||||||
|
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
|
||||||
|
highlight_code = false
|
||||||
|
|
||||||
|
# Whether to build a search index to be used later on by a JavaScript library
|
||||||
|
build_search_index = false
|
||||||
|
|
||||||
|
languages = [
|
||||||
|
{code = "fr"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
# Put all your custom variables here
|
5
test_site_i18n/content/base.fr.md
Normal file
5
test_site_i18n/content/base.fr.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
title = "Une page"
|
||||||
|
+++
|
||||||
|
|
||||||
|
Une page en Français
|
5
test_site_i18n/content/base.md
Normal file
5
test_site_i18n/content/base.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
title = "A page"
|
||||||
|
+++
|
||||||
|
|
||||||
|
A page in english
|
4
test_site_i18n/content/blog/_index.fr.md
Normal file
4
test_site_i18n/content/blog/_index.fr.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
+++
|
||||||
|
sort_by = "date"
|
||||||
|
insert_anchors = "right"
|
||||||
|
+++
|
4
test_site_i18n/content/blog/_index.md
Normal file
4
test_site_i18n/content/blog/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
+++
|
||||||
|
sort_by = "date"
|
||||||
|
insert_anchors = "left"
|
||||||
|
+++
|
7
test_site_i18n/content/blog/fixed-slug.fr.md
Normal file
7
test_site_i18n/content/blog/fixed-slug.fr.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title = "Un slug fix"
|
||||||
|
slug = "something-else"
|
||||||
|
date = 2017-01-01
|
||||||
|
+++
|
||||||
|
|
||||||
|
Une page qui definit son slug dans le front-matter
|
12
test_site_i18n/content/blog/fixed-slug.md
Normal file
12
test_site_i18n/content/blog/fixed-slug.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
+++
|
||||||
|
title = "Fixed slug"
|
||||||
|
slug = "something-else"
|
||||||
|
date = 2017-01-01
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page with a slug defined
|
||||||
|
|
||||||
|
# Title
|
||||||
|
|
||||||
|
Hey
|
||||||
|
|
5
test_site_i18n/content/blog/not-in-frend.md
Normal file
5
test_site_i18n/content/blog/not-in-frend.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
date = 2018-08-19
|
||||||
|
+++
|
||||||
|
|
||||||
|
Something not translated
|
6
test_site_i18n/content/blog/something.fr.md
Normal file
6
test_site_i18n/content/blog/something.fr.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Quelque chose"
|
||||||
|
date = 2018-10-09
|
||||||
|
+++
|
||||||
|
|
||||||
|
Un article
|
6
test_site_i18n/content/blog/something.md
Normal file
6
test_site_i18n/content/blog/something.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Something"
|
||||||
|
date = 2018-10-09
|
||||||
|
+++
|
||||||
|
|
||||||
|
A blog post
|
5
test_site_i18n/content/blog/with-assets/index.fr.md
Normal file
5
test_site_i18n/content/blog/with-assets/index.fr.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
date = 2018-11-10
|
||||||
|
+++
|
||||||
|
|
||||||
|
Avec des fichiers
|
5
test_site_i18n/content/blog/with-assets/index.md
Normal file
5
test_site_i18n/content/blog/with-assets/index.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
date = 2018-11-10
|
||||||
|
+++
|
||||||
|
|
||||||
|
With assets
|
3
test_site_i18n/templates/index.html
Normal file
3
test_site_i18n/templates/index.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% for page in section.pages %}
|
||||||
|
{{page.title}}
|
||||||
|
{% endfor %}
|
Loading…
Reference in a new issue