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
|
||||
.idea/
|
||||
test_site/public
|
||||
test_site_i18n/public
|
||||
docs/public
|
||||
|
||||
small-blog
|
||||
|
|
|
@ -31,6 +31,8 @@ pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> {
|
|||
pub struct FileInfo {
|
||||
/// The full path to the .md file
|
||||
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
|
||||
/// Doesn't contain the language if there was one in the filename
|
||||
pub name: String,
|
||||
|
@ -68,6 +70,7 @@ impl FileInfo {
|
|||
}
|
||||
|
||||
FileInfo {
|
||||
filename: file_path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
path: file_path,
|
||||
// We don't care about grand parent for pages
|
||||
grand_parent: None,
|
||||
|
@ -79,6 +82,7 @@ impl FileInfo {
|
|||
}
|
||||
|
||||
pub fn new_section(path: &Path) -> FileInfo {
|
||||
let file_path = path.to_path_buf();
|
||||
let parent = path.parent().unwrap().to_path_buf();
|
||||
let name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||
let components = find_content_components(path);
|
||||
|
@ -90,7 +94,8 @@ impl FileInfo {
|
|||
let grand_parent = parent.parent().map(|p| p.to_path_buf());
|
||||
|
||||
FileInfo {
|
||||
path: path.to_path_buf(),
|
||||
filename: file_path.file_name().unwrap().to_string_lossy().to_string(),
|
||||
path: file_path,
|
||||
parent,
|
||||
grand_parent,
|
||||
name,
|
||||
|
@ -136,6 +141,7 @@ impl Default for FileInfo {
|
|||
path: PathBuf::new(),
|
||||
parent: PathBuf::new(),
|
||||
grand_parent: None,
|
||||
filename: String::new(),
|
||||
name: String::new(),
|
||||
components: vec![],
|
||||
relative: String::new(),
|
||||
|
|
|
@ -80,14 +80,12 @@ impl Library {
|
|||
/// Find out the direct subsections of each subsection if there are some
|
||||
/// as well as the pages for each section
|
||||
pub fn populate_sections(&mut self) {
|
||||
let (root_path, index_path) = self
|
||||
let root_path= self
|
||||
.sections
|
||||
.values()
|
||||
.find(|s| s.is_index())
|
||||
.map(|s| (s.file.parent.clone(), s.file.path.clone()))
|
||||
.map(|s| s.file.parent.clone())
|
||||
.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
|
||||
let mut ancestors: 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 {
|
||||
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![])
|
||||
.push(section.file.path.clone());
|
||||
}
|
||||
|
@ -111,6 +110,7 @@ impl Library {
|
|||
}
|
||||
|
||||
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
|
||||
let mut parents = vec![root_key];
|
||||
for component in §ion.file.components {
|
||||
|
@ -119,7 +119,7 @@ impl Library {
|
|||
if path == section.file.parent {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,12 @@ impl Library {
|
|||
}
|
||||
|
||||
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) {
|
||||
let parent_is_transparent;
|
||||
// We need to get a reference to a section later so keep the scope of borrowing small
|
||||
|
@ -158,9 +163,9 @@ impl Library {
|
|||
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() {
|
||||
Some(parent) => parent_section_path = parent.join("_index.md"),
|
||||
Some(parent) => parent_section_path = parent.join(&parent_filename),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,9 +147,14 @@ impl Site {
|
|||
Ok(site)
|
||||
}
|
||||
|
||||
/// The index section is ALWAYS at that path
|
||||
pub fn index_section_path(&self) -> PathBuf {
|
||||
self.content_path.join("_index.md")
|
||||
/// The index sections are ALWAYS at those paths
|
||||
/// There are one index section for the basic language + 1 per language
|
||||
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
|
||||
|
@ -184,7 +189,7 @@ impl Site {
|
|||
.unwrap()
|
||||
.filter_map(|e| e.ok())
|
||||
.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());
|
||||
|
||||
|
@ -219,9 +224,9 @@ impl Site {
|
|||
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
|
||||
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 self.config.build_search_index && !index_section.meta.in_search_index {
|
||||
bail!(
|
||||
|
@ -234,19 +239,30 @@ impl Site {
|
|||
// Not in else because of borrow checker
|
||||
if !self.library.contains_section(&index_path) {
|
||||
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.file.path = self.content_path.join("_index.md");
|
||||
index_section.file.parent = self.content_path.clone();
|
||||
index_section.file.relative = "_index.md".to_string();
|
||||
}
|
||||
self.library.insert_section(index_section);
|
||||
}
|
||||
}
|
||||
|
||||
let mut pages_insert_anchors = HashMap::new();
|
||||
for page in pages {
|
||||
let p = page?;
|
||||
pages_insert_anchors.insert(
|
||||
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)?;
|
||||
}
|
||||
|
@ -274,7 +290,7 @@ impl Site {
|
|||
for (_, p) in self.library.pages() {
|
||||
pages_insert_anchors.insert(
|
||||
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>> {
|
||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
||||
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)?;
|
||||
}
|
||||
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`.
|
||||
/// Defaults to `AnchorInsert::None` if no parent section found
|
||||
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf) -> InsertAnchor {
|
||||
match self.library.get_section(&parent_path.join("_index.md")) {
|
||||
pub fn find_parent_section_insert_anchor(&self, parent_path: &PathBuf, lang: &Option<String>) -> InsertAnchor {
|
||||
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,
|
||||
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