From 1d06324a6533c8963c12bb71986c05e5c1ddfbfc Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 28 Dec 2018 12:15:17 +0100 Subject: [PATCH] Load multi-languages pages/sections --- .gitignore | 1 + components/library/src/content/file_info.rs | 8 ++- components/library/src/library.rs | 23 ++++--- components/site/src/lib.rs | 69 ++++++++++++------- components/site/tests/site_i18n.rs | 46 +++++++++++++ test_site_i18n/config.toml | 19 +++++ test_site_i18n/content/base.fr.md | 5 ++ test_site_i18n/content/base.md | 5 ++ test_site_i18n/content/blog/_index.fr.md | 4 ++ test_site_i18n/content/blog/_index.md | 4 ++ test_site_i18n/content/blog/fixed-slug.fr.md | 7 ++ test_site_i18n/content/blog/fixed-slug.md | 12 ++++ test_site_i18n/content/blog/not-in-frend.md | 5 ++ test_site_i18n/content/blog/something.fr.md | 6 ++ test_site_i18n/content/blog/something.md | 6 ++ .../content/blog/with-assets/index.fr.md | 5 ++ .../content/blog/with-assets/index.md | 5 ++ test_site_i18n/templates/index.html | 3 + 18 files changed, 199 insertions(+), 34 deletions(-) create mode 100644 components/site/tests/site_i18n.rs create mode 100644 test_site_i18n/config.toml create mode 100644 test_site_i18n/content/base.fr.md create mode 100644 test_site_i18n/content/base.md create mode 100644 test_site_i18n/content/blog/_index.fr.md create mode 100644 test_site_i18n/content/blog/_index.md create mode 100644 test_site_i18n/content/blog/fixed-slug.fr.md create mode 100644 test_site_i18n/content/blog/fixed-slug.md create mode 100644 test_site_i18n/content/blog/not-in-frend.md create mode 100644 test_site_i18n/content/blog/something.fr.md create mode 100644 test_site_i18n/content/blog/something.md create mode 100644 test_site_i18n/content/blog/with-assets/index.fr.md create mode 100644 test_site_i18n/content/blog/with-assets/index.md create mode 100644 test_site_i18n/templates/index.html diff --git a/.gitignore b/.gitignore index ef376f6..74d95af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ target .idea/ test_site/public +test_site_i18n/public docs/public small-blog diff --git a/components/library/src/content/file_info.rs b/components/library/src/content/file_info.rs index 4bc2237..dc887a8 100644 --- a/components/library/src/content/file_info.rs +++ b/components/library/src/content/file_info.rs @@ -31,6 +31,8 @@ pub fn find_content_components>(path: P) -> Vec { 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(), diff --git a/components/library/src/library.rs b/components/library/src/library.rs index 05971f8..3ff6f2f 100644 --- a/components/library/src/library.rs +++ b/components/library/src/library.rs @@ -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> = HashMap::new(); let mut subsections: HashMap> = 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, } } diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs index d46f1c5..ebed46e 100644 --- a/components/site/src/lib.rs +++ b/components/site/src/lib.rs @@ -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)> { + 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,26 +224,37 @@ 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(); - 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!( + 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!( "You have enabled search in the config but disabled it in the index section: \ either turn off the search in the config or remote `in_search_index = true` from the \ section front-matter." - ) + ) + } + } + // 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.relative = "_index.md".to_string(); + } + self.library.insert_section(index_section); } - } - // Not in else because of borrow checker - if !self.library.contains_section(&index_path) { - let mut index_section = Section::default(); - 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(); @@ -246,7 +262,7 @@ impl Site { 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> { 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) -> 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, } diff --git a/components/site/tests/site_i18n.rs b/components/site/tests/site_i18n.rs new file mode 100644 index 0000000..dde0b81 --- /dev/null +++ b/components/site/tests/site_i18n.rs @@ -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())); + } +} diff --git a/test_site_i18n/config.toml b/test_site_i18n/config.toml new file mode 100644 index 0000000..c5dd0f0 --- /dev/null +++ b/test_site_i18n/config.toml @@ -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 diff --git a/test_site_i18n/content/base.fr.md b/test_site_i18n/content/base.fr.md new file mode 100644 index 0000000..2ca2fc7 --- /dev/null +++ b/test_site_i18n/content/base.fr.md @@ -0,0 +1,5 @@ ++++ +title = "Une page" ++++ + +Une page en Français diff --git a/test_site_i18n/content/base.md b/test_site_i18n/content/base.md new file mode 100644 index 0000000..b759c2d --- /dev/null +++ b/test_site_i18n/content/base.md @@ -0,0 +1,5 @@ ++++ +title = "A page" ++++ + +A page in english diff --git a/test_site_i18n/content/blog/_index.fr.md b/test_site_i18n/content/blog/_index.fr.md new file mode 100644 index 0000000..b19438a --- /dev/null +++ b/test_site_i18n/content/blog/_index.fr.md @@ -0,0 +1,4 @@ ++++ +sort_by = "date" +insert_anchors = "right" ++++ diff --git a/test_site_i18n/content/blog/_index.md b/test_site_i18n/content/blog/_index.md new file mode 100644 index 0000000..028555a --- /dev/null +++ b/test_site_i18n/content/blog/_index.md @@ -0,0 +1,4 @@ ++++ +sort_by = "date" +insert_anchors = "left" ++++ diff --git a/test_site_i18n/content/blog/fixed-slug.fr.md b/test_site_i18n/content/blog/fixed-slug.fr.md new file mode 100644 index 0000000..9a2ba32 --- /dev/null +++ b/test_site_i18n/content/blog/fixed-slug.fr.md @@ -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 diff --git a/test_site_i18n/content/blog/fixed-slug.md b/test_site_i18n/content/blog/fixed-slug.md new file mode 100644 index 0000000..f51ed29 --- /dev/null +++ b/test_site_i18n/content/blog/fixed-slug.md @@ -0,0 +1,12 @@ ++++ +title = "Fixed slug" +slug = "something-else" +date = 2017-01-01 ++++ + +A simple page with a slug defined + +# Title + +Hey + diff --git a/test_site_i18n/content/blog/not-in-frend.md b/test_site_i18n/content/blog/not-in-frend.md new file mode 100644 index 0000000..8c06035 --- /dev/null +++ b/test_site_i18n/content/blog/not-in-frend.md @@ -0,0 +1,5 @@ ++++ +date = 2018-08-19 ++++ + +Something not translated diff --git a/test_site_i18n/content/blog/something.fr.md b/test_site_i18n/content/blog/something.fr.md new file mode 100644 index 0000000..bfb19b8 --- /dev/null +++ b/test_site_i18n/content/blog/something.fr.md @@ -0,0 +1,6 @@ ++++ +title = "Quelque chose" +date = 2018-10-09 ++++ + +Un article diff --git a/test_site_i18n/content/blog/something.md b/test_site_i18n/content/blog/something.md new file mode 100644 index 0000000..ee990de --- /dev/null +++ b/test_site_i18n/content/blog/something.md @@ -0,0 +1,6 @@ ++++ +title = "Something" +date = 2018-10-09 ++++ + +A blog post diff --git a/test_site_i18n/content/blog/with-assets/index.fr.md b/test_site_i18n/content/blog/with-assets/index.fr.md new file mode 100644 index 0000000..550ca78 --- /dev/null +++ b/test_site_i18n/content/blog/with-assets/index.fr.md @@ -0,0 +1,5 @@ ++++ +date = 2018-11-10 ++++ + +Avec des fichiers diff --git a/test_site_i18n/content/blog/with-assets/index.md b/test_site_i18n/content/blog/with-assets/index.md new file mode 100644 index 0000000..ada03fa --- /dev/null +++ b/test_site_i18n/content/blog/with-assets/index.md @@ -0,0 +1,5 @@ ++++ +date = 2018-11-10 ++++ + +With assets diff --git a/test_site_i18n/templates/index.html b/test_site_i18n/templates/index.html new file mode 100644 index 0000000..f2e96a5 --- /dev/null +++ b/test_site_i18n/templates/index.html @@ -0,0 +1,3 @@ +{% for page in section.pages %} + {{page.title}} +{% endfor %}