Add multilingual taxonomies
This commit is contained in:
parent
cae9223ebd
commit
538866487b
387
Cargo.lock
generated
387
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
3
test_site_i18n/templates/auteurs/list.html
Normal file
3
test_site_i18n/templates/auteurs/list.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% for author in terms %}
|
||||
{{ author.name }} {{ author.slug }} {{ author.pages | length }}
|
||||
{% endfor %}
|
21
test_site_i18n/templates/auteurs/single.html
Normal file
21
test_site_i18n/templates/auteurs/single.html
Normal file
|
@ -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 %}
|
3
test_site_i18n/templates/authors/list.html
Normal file
3
test_site_i18n/templates/authors/list.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
{% for term in terms %}
|
||||
{{ term.name }} {{ term.slug }} {{ term.pages | length }}
|
||||
{% endfor %}
|
21
test_site_i18n/templates/authors/single.html
Normal file
21
test_site_i18n/templates/authors/single.html
Normal file
|
@ -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 %}
|
Loading…
Reference in a new issue