@@ -42,6 +42,8 @@ pub struct Taxonomy { | |||
pub paginate_path: Option<String>, | |||
/// Whether to generate a RSS feed only for each taxonomy term, defaults to false | |||
pub rss: bool, | |||
/// The language for that taxonomy, only used in multilingual sites | |||
pub lang: Option<String>, | |||
} | |||
impl Taxonomy { | |||
@@ -64,7 +66,7 @@ impl Taxonomy { | |||
impl Default for Taxonomy { | |||
fn default() -> Taxonomy { | |||
Taxonomy { name: String::new(), paginate_by: None, paginate_path: None, rss: false } | |||
Taxonomy { name: String::new(), paginate_by: None, paginate_path: None, rss: false, lang: None } | |||
} | |||
} | |||
@@ -48,7 +48,7 @@ pub struct TaxonomyItem { | |||
} | |||
impl TaxonomyItem { | |||
pub fn new(name: &str, path: &str, config: &Config, keys: Vec<Key>, library: &Library) -> Self { | |||
pub fn new(name: &str, taxonomy: &TaxonomyConfig, config: &Config, keys: Vec<Key>, library: &Library) -> Self { | |||
// Taxonomy are almost always used for blogs so we filter by dates | |||
// and it's not like we can sort things across sections by anything other | |||
// than dates | |||
@@ -64,7 +64,11 @@ impl TaxonomyItem { | |||
.collect(); | |||
let (mut pages, ignored_pages) = sort_pages_by_date(data); | |||
let slug = slugify(name); | |||
let permalink = config.make_permalink(&format!("/{}/{}", path, slug)); | |||
let permalink = if let Some(ref lang) = taxonomy.lang { | |||
config.make_permalink(&format!("/{}/{}/{}", lang, taxonomy.name, slug)) | |||
} else { | |||
config.make_permalink(&format!("/{}/{}", taxonomy.name, slug)) | |||
}; | |||
// We still append pages without dates at the end | |||
pages.extend(ignored_pages); | |||
@@ -108,7 +112,7 @@ impl Taxonomy { | |||
) -> Taxonomy { | |||
let mut sorted_items = vec![]; | |||
for (name, pages) in items { | |||
sorted_items.push(TaxonomyItem::new(&name, &kind.name, config, pages, library)); | |||
sorted_items.push(TaxonomyItem::new(&name, &kind, config, pages, library)); | |||
} | |||
sorted_items.sort_by(|a, b| a.name.cmp(&b.name)); | |||
@@ -186,6 +190,14 @@ pub fn find_taxonomies(config: &Config, library: &Library) -> Result<Vec<Taxonom | |||
for (name, val) in &page.meta.taxonomies { | |||
if taxonomies_def.contains_key(name) { | |||
if taxonomies_def[name].lang != page.lang { | |||
bail!( | |||
"Page `{}` has taxonomy `{}` which is not available in that language", | |||
page.file.path.display(), | |||
name | |||
); | |||
} | |||
all_taxonomies.entry(name).or_insert_with(HashMap::new); | |||
for v in val { | |||
@@ -220,7 +232,7 @@ mod tests { | |||
use super::*; | |||
use std::collections::HashMap; | |||
use config::{Config, Taxonomy as TaxonomyConfig}; | |||
use config::{Config, Taxonomy as TaxonomyConfig, Language}; | |||
use content::Page; | |||
use library::Library; | |||
@@ -326,4 +338,112 @@ mod tests { | |||
"Page `` has taxonomy `tags` which is not defined in config.toml" | |||
); | |||
} | |||
#[test] | |||
fn can_make_taxonomies_in_multiple_languages() { | |||
let mut config = Config::default(); | |||
config.languages.push(Language {rss: false, code: "fr".to_string()}); | |||
let mut library = Library::new(2, 0, true); | |||
config.taxonomies = vec![ | |||
TaxonomyConfig { name: "categories".to_string(), ..TaxonomyConfig::default() }, | |||
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }, | |||
TaxonomyConfig { name: "auteurs".to_string(), lang: Some("fr".to_string()), ..TaxonomyConfig::default() }, | |||
]; | |||
let mut page1 = Page::default(); | |||
let mut taxo_page1 = HashMap::new(); | |||
taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]); | |||
taxo_page1.insert("categories".to_string(), vec!["Programming tutorials".to_string()]); | |||
page1.meta.taxonomies = taxo_page1; | |||
library.insert_page(page1); | |||
let mut page2 = Page::default(); | |||
let mut taxo_page2 = HashMap::new(); | |||
taxo_page2.insert("tags".to_string(), vec!["rust".to_string()]); | |||
taxo_page2.insert("categories".to_string(), vec!["Other".to_string()]); | |||
page2.meta.taxonomies = taxo_page2; | |||
library.insert_page(page2); | |||
let mut page3 = Page::default(); | |||
page3.lang = Some("fr".to_string()); | |||
let mut taxo_page3 = HashMap::new(); | |||
taxo_page3.insert("auteurs".to_string(), vec!["Vincent Prouillet".to_string()]); | |||
page3.meta.taxonomies = taxo_page3; | |||
library.insert_page(page3); | |||
let taxonomies = find_taxonomies(&config, &library).unwrap(); | |||
let (tags, categories, authors) = { | |||
let mut t = None; | |||
let mut c = None; | |||
let mut a = None; | |||
for x in taxonomies { | |||
match x.kind.name.as_ref() { | |||
"tags" => t = Some(x), | |||
"categories" => c = Some(x), | |||
"auteurs" => a = Some(x), | |||
_ => unreachable!(), | |||
} | |||
} | |||
(t.unwrap(), c.unwrap(), a.unwrap()) | |||
}; | |||
assert_eq!(tags.items.len(), 2); | |||
assert_eq!(categories.items.len(), 2); | |||
assert_eq!(authors.items.len(), 1); | |||
assert_eq!(tags.items[0].name, "db"); | |||
assert_eq!(tags.items[0].slug, "db"); | |||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/"); | |||
assert_eq!(tags.items[0].pages.len(), 1); | |||
assert_eq!(tags.items[1].name, "rust"); | |||
assert_eq!(tags.items[1].slug, "rust"); | |||
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/rust/"); | |||
assert_eq!(tags.items[1].pages.len(), 2); | |||
assert_eq!(authors.items[0].name, "Vincent Prouillet"); | |||
assert_eq!(authors.items[0].slug, "vincent-prouillet"); | |||
assert_eq!(authors.items[0].permalink, "http://a-website.com/fr/auteurs/vincent-prouillet/"); | |||
assert_eq!(authors.items[0].pages.len(), 1); | |||
assert_eq!(categories.items[0].name, "Other"); | |||
assert_eq!(categories.items[0].slug, "other"); | |||
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/"); | |||
assert_eq!(categories.items[0].pages.len(), 1); | |||
assert_eq!(categories.items[1].name, "Programming tutorials"); | |||
assert_eq!(categories.items[1].slug, "programming-tutorials"); | |||
assert_eq!( | |||
categories.items[1].permalink, | |||
"http://a-website.com/categories/programming-tutorials/" | |||
); | |||
assert_eq!(categories.items[1].pages.len(), 1); | |||
} | |||
#[test] | |||
fn errors_on_taxonomy_of_different_language() { | |||
let mut config = Config::default(); | |||
config.languages.push(Language {rss: false, code: "fr".to_string()}); | |||
let mut library = Library::new(2, 0, false); | |||
config.taxonomies = | |||
vec![TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }]; | |||
let mut page1 = Page::default(); | |||
page1.lang = Some("fr".to_string()); | |||
let mut taxo_page1 = HashMap::new(); | |||
taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]); | |||
page1.meta.taxonomies = taxo_page1; | |||
library.insert_page(page1); | |||
let taxonomies = find_taxonomies(&config, &library); | |||
assert!(taxonomies.is_err()); | |||
let err = taxonomies.unwrap_err(); | |||
// no path as this is created by Default | |||
assert_eq!( | |||
err.description(), | |||
"Page `` has taxonomy `tags` which is not available in that language" | |||
); | |||
} | |||
} |
@@ -723,7 +723,13 @@ impl Site { | |||
} | |||
ensure_directory_exists(&self.output_path)?; | |||
let output_path = self.output_path.join(&taxonomy.kind.name); | |||
let output_path = if let Some(ref lang) = taxonomy.kind.lang { | |||
let mid_path = self.output_path.join(lang); | |||
create_directory(&mid_path)?; | |||
mid_path.join(&taxonomy.kind.name) | |||
} else { | |||
self.output_path.join(&taxonomy.kind.name) | |||
}; | |||
let list_output = taxonomy.render_all_terms(&self.tera, &self.config, &self.library)?; | |||
create_directory(&output_path)?; | |||
create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?; | |||
@@ -479,6 +479,7 @@ fn can_build_site_with_pagination_for_taxonomy() { | |||
paginate_by: Some(2), | |||
paginate_path: None, | |||
rss: true, | |||
lang: None, | |||
}); | |||
site.load().unwrap(); | |||
@@ -125,4 +125,18 @@ fn can_build_multilingual_site() { | |||
assert!(file_contains!(public, "fr/rss.xml", "https://example.com/fr/blog/something-else/")); | |||
// Italian doesn't have RSS enabled | |||
assert!(!file_exists!(public, "it/rss.xml")); | |||
// Taxonomies are per-language | |||
assert!(file_exists!(public, "authors/index.html")); | |||
assert!(file_contains!(public, "authors/index.html", "Queen")); | |||
assert!(!file_contains!(public, "authors/index.html", "Vincent")); | |||
assert!(!file_exists!(public, "auteurs/index.html")); | |||
assert!(file_exists!(public, "authors/queen-elizabeth/rss.xml")); | |||
assert!(!file_exists!(public, "fr/authors/index.html")); | |||
assert!(file_exists!(public, "fr/auteurs/index.html")); | |||
assert!(!file_contains!(public, "fr/auteurs/index.html", "Queen")); | |||
assert!(file_contains!(public, "fr/auteurs/index.html", "Vincent")); | |||
assert!(!file_exists!(public, "fr/auteurs/vincent-prouillet/rss.xml")); | |||
} |
@@ -297,7 +297,7 @@ mod tests { | |||
fn can_get_taxonomy() { | |||
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }; | |||
let library = Library::new(0, 0, false); | |||
let tag = TaxonomyItem::new("Programming", "tags", &Config::default(), vec![], &library); | |||
let tag = TaxonomyItem::new("Programming", &taxo_config, &Config::default(), vec![], &library); | |||
let tags = Taxonomy { kind: taxo_config, items: vec![tag] }; | |||
let taxonomies = vec![tags.clone()]; | |||
@@ -336,7 +336,7 @@ mod tests { | |||
fn can_get_taxonomy_url() { | |||
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }; | |||
let library = Library::new(0, 0, false); | |||
let tag = TaxonomyItem::new("Programming", "tags", &Config::default(), vec![], &library); | |||
let tag = TaxonomyItem::new("Programming", &taxo_config, &Config::default(), vec![], &library); | |||
let tags = Taxonomy { kind: taxo_config, items: vec![tag] }; | |||
let taxonomies = vec![tags.clone()]; | |||
@@ -16,6 +16,9 @@ languages = [ | |||
] | |||
``` | |||
If you want to use per-language taxonomies, ensure you set the `lang` field in their | |||
configuration. | |||
## Content | |||
Once the languages are added in, you can start to translate your content. Zola | |||
uses the filename to detect the language: | |||
@@ -7,13 +7,14 @@ Zola has built-in support for taxonomies. | |||
The first step is to define the taxonomies in your [config.toml](./documentation/getting-started/configuration.md). | |||
A taxonomy has 4 variables: | |||
A taxonomy has 5 variables: | |||
- `name`: a required string that will be used in the URLs, usually the plural version (i.e. tags, categories etc) | |||
- `paginate_by`: if this is set to a number, each term page will be paginated by this much. | |||
- `paginate_path`: if set, will be the path used by paginated page and the page number will be appended after it. | |||
For example the default would be page/1 | |||
- `rss`: if set to `true`, a RSS feed will be generated for each individual term. | |||
- `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for | |||
Once this is done, you can then set taxonomies in your content and Zola will pick | |||
them up: | |||
@@ -13,6 +13,11 @@ build_search_index = false | |||
generate_rss = true | |||
taxonomies = [ | |||
{name = "authors", rss = true}, | |||
{name = "auteurs", lang = "fr"}, | |||
] | |||
languages = [ | |||
{code = "fr", rss = true}, | |||
{code = "it", rss = false}, | |||
@@ -1,6 +1,9 @@ | |||
+++ | |||
title = "Quelque chose" | |||
date = 2018-10-09 | |||
[taxonomies] | |||
auteurs = ["Vincent Prouillet"] | |||
+++ | |||
Un article |
@@ -1,6 +1,9 @@ | |||
+++ | |||
title = "Something" | |||
date = 2018-10-09 | |||
[taxonomies] | |||
authors = ["Queen Elizabeth"] | |||
+++ | |||
A blog post |
@@ -0,0 +1,3 @@ | |||
{% for author in terms %} | |||
{{ author.name }} {{ author.slug }} {{ author.pages | length }} | |||
{% endfor %} |
@@ -0,0 +1,21 @@ | |||
{% if not paginator %} | |||
Tag: {{ term.name }} | |||
{% for page in term.pages %} | |||
<article> | |||
<h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3> | |||
</article> | |||
{% endfor %} | |||
{% else %} | |||
Tag: {{ term.name }} | |||
{% for page in paginator.pages %} | |||
{{page.title|safe}} | |||
{% endfor %} | |||
Num pagers: {{ paginator.number_pagers }} | |||
Page size: {{ paginator.paginate_by }} | |||
Current index: {{ paginator.current_index }} | |||
First: {{ paginator.first | safe }} | |||
Last: {{ paginator.last | safe }} | |||
{% if paginator.previous %}has_prev{% endif%} | |||
{% if paginator.next %}has_next{% endif%} | |||
{% endif %} |
@@ -0,0 +1,3 @@ | |||
{% for term in terms %} | |||
{{ term.name }} {{ term.slug }} {{ term.pages | length }} | |||
{% endfor %} |
@@ -0,0 +1,21 @@ | |||
{% if not paginator %} | |||
Tag: {{ term.name }} | |||
{% for page in term.pages %} | |||
<article> | |||
<h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3> | |||
</article> | |||
{% endfor %} | |||
{% else %} | |||
Tag: {{ term.name }} | |||
{% for page in paginator.pages %} | |||
{{page.title|safe}} | |||
{% endfor %} | |||
Num pagers: {{ paginator.number_pagers }} | |||
Page size: {{ paginator.paginate_by }} | |||
Current index: {{ paginator.current_index }} | |||
First: {{ paginator.first | safe }} | |||
Last: {{ paginator.last | safe }} | |||
{% if paginator.previous %}has_prev{% endif%} | |||
{% if paginator.next %}has_next{% endif%} | |||
{% endif %} |