Skip to content

Commit 41fa26e

Browse files
SumDonkuSveluca93
authored andcommitted
Issue 2359 get section by lang (getzola#2410)
* adding optional `lang` arugment to `get_section` global function * Add handling of default language passed in `lang` argument of `get_section` * Remove clones for path. Change "?" to an explicit check for error * lint changes * Clean up error handling for add_lang_to_path call * fix format * Add optional parameter "lang" to get_page template function. Add check for language available in config. * Modify helper function name from calculate_path to get_path_with_lang. Modify documentation for get_section and get_page to include equivalent calls without using lang argument to demostrate how lang argument effects pathing.
1 parent 221ba5b commit 41fa26e

File tree

3 files changed

+290
-25
lines changed

3 files changed

+290
-25
lines changed

components/site/src/tpls.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::Site;
22
use libs::tera::Result as TeraResult;
3+
use std::sync::Arc;
34
use templates::{filters, global_fns};
45

56
/// Adds global fns that are to be available to shortcodes while rendering markdown
@@ -74,13 +75,25 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
7475

7576
/// Functions filled once we have parsed all the pages/sections only, so not available in shortcodes
7677
pub fn register_tera_global_fns(site: &mut Site) {
78+
let language_list: Arc<Vec<String>> =
79+
Arc::new(site.config.languages.keys().map(|s| s.to_string()).collect());
7780
site.tera.register_function(
7881
"get_page",
79-
global_fns::GetPage::new(site.base_path.clone(), site.library.clone()),
82+
global_fns::GetPage::new(
83+
site.base_path.clone(),
84+
&site.config.default_language,
85+
Arc::clone(&language_list),
86+
site.library.clone(),
87+
),
8088
);
8189
site.tera.register_function(
8290
"get_section",
83-
global_fns::GetSection::new(site.base_path.clone(), site.library.clone()),
91+
global_fns::GetSection::new(
92+
site.base_path.clone(),
93+
&site.config.default_language,
94+
Arc::clone(&language_list),
95+
site.library.clone(),
96+
),
8497
);
8598
site.tera.register_function(
8699
"get_taxonomy",

components/templates/src/global_fns/content.rs

+251-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use content::{Library, Taxonomy, TaxonomyTerm};
22
use libs::tera::{from_value, to_value, Function as TeraFn, Result, Value};
3+
use std::borrow::Cow;
34
use std::collections::HashMap;
45
use std::path::PathBuf;
56
use std::sync::{Arc, RwLock};
@@ -71,14 +72,60 @@ impl TeraFn for GetTaxonomyUrl {
7172
}
7273
}
7374

75+
fn add_lang_to_path<'a>(path: &str, lang: &str) -> Result<Cow<'a, String>> {
76+
match path.rfind('.') {
77+
Some(period_offset) => {
78+
let prefix = path.get(0..period_offset);
79+
let suffix = path.get(period_offset..);
80+
if prefix.is_none() || suffix.is_none() {
81+
Err(format!("Error adding language code to {}", path).into())
82+
} else {
83+
Ok(Cow::Owned(format!("{}.{}{}", prefix.unwrap(), lang, suffix.unwrap())))
84+
}
85+
}
86+
None => Ok(Cow::Owned(format!("{}.{}", path, lang))),
87+
}
88+
}
89+
90+
fn get_path_with_lang<'a>(
91+
path: &'a String,
92+
lang: &Option<String>,
93+
default_lang: &str,
94+
supported_languages: &[String],
95+
) -> Result<Cow<'a, String>> {
96+
if supported_languages.contains(&default_lang.to_string()) {
97+
lang.as_ref().map_or_else(
98+
|| Ok(Cow::Borrowed(path)),
99+
|lang_code| match default_lang == lang_code {
100+
true => Ok(Cow::Borrowed(path)),
101+
false => add_lang_to_path(path, lang_code),
102+
},
103+
)
104+
} else {
105+
Err(format!("Unsupported language {}", default_lang).into())
106+
}
107+
}
108+
74109
#[derive(Debug)]
75110
pub struct GetPage {
76111
base_path: PathBuf,
112+
default_lang: String,
113+
supported_languages: Arc<Vec<String>>,
77114
library: Arc<RwLock<Library>>,
78115
}
79116
impl GetPage {
80-
pub fn new(base_path: PathBuf, library: Arc<RwLock<Library>>) -> Self {
81-
Self { base_path: base_path.join("content"), library }
117+
pub fn new(
118+
base_path: PathBuf,
119+
default_lang: &str,
120+
supported_languages: Arc<Vec<String>>,
121+
library: Arc<RwLock<Library>>,
122+
) -> Self {
123+
Self {
124+
base_path: base_path.join("content"),
125+
default_lang: default_lang.to_string(),
126+
supported_languages,
127+
library,
128+
}
82129
}
83130
}
84131
impl TeraFn for GetPage {
@@ -88,23 +135,50 @@ impl TeraFn for GetPage {
88135
args.get("path"),
89136
"`get_page` requires a `path` argument with a string value"
90137
);
91-
let full_path = self.base_path.join(&path);
92-
let library = self.library.read().unwrap();
93-
match library.pages.get(&full_path) {
94-
Some(p) => Ok(to_value(p.serialize(&library)).unwrap()),
95-
None => Err(format!("Page `{}` not found.", path).into()),
96-
}
138+
139+
let lang =
140+
optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string");
141+
142+
get_path_with_lang(&path, &lang, &self.default_lang, &self.supported_languages).and_then(
143+
|path_with_lang| {
144+
let full_path = self.base_path.join(path_with_lang.as_ref());
145+
let library = self.library.read().unwrap();
146+
147+
match library.pages.get(&full_path) {
148+
Some(p) => Ok(to_value(p.serialize(&library)).unwrap()),
149+
None => match lang {
150+
Some(lang_code) => {
151+
Err(format!("Page `{}` not found for language `{}`.", path, lang_code)
152+
.into())
153+
}
154+
None => Err(format!("Page `{}` not found.", path).into()),
155+
},
156+
}
157+
},
158+
)
97159
}
98160
}
99161

100162
#[derive(Debug)]
101163
pub struct GetSection {
102164
base_path: PathBuf,
165+
default_lang: String,
166+
supported_languages: Arc<Vec<String>>,
103167
library: Arc<RwLock<Library>>,
104168
}
105169
impl GetSection {
106-
pub fn new(base_path: PathBuf, library: Arc<RwLock<Library>>) -> Self {
107-
Self { base_path: base_path.join("content"), library }
170+
pub fn new(
171+
base_path: PathBuf,
172+
default_lang: &str,
173+
supported_languages: Arc<Vec<String>>,
174+
library: Arc<RwLock<Library>>,
175+
) -> Self {
176+
Self {
177+
base_path: base_path.join("content"),
178+
default_lang: default_lang.to_string(),
179+
supported_languages,
180+
library,
181+
}
108182
}
109183
}
110184
impl TeraFn for GetSection {
@@ -119,19 +193,32 @@ impl TeraFn for GetSection {
119193
.get("metadata_only")
120194
.map_or(false, |c| from_value::<bool>(c.clone()).unwrap_or(false));
121195

122-
let full_path = self.base_path.join(&path);
123-
let library = self.library.read().unwrap();
124-
125-
match library.sections.get(&full_path) {
126-
Some(s) => {
127-
if metadata_only {
128-
Ok(to_value(s.serialize_basic(&library)).unwrap())
129-
} else {
130-
Ok(to_value(s.serialize(&library)).unwrap())
196+
let lang =
197+
optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string");
198+
199+
get_path_with_lang(&path, &lang, self.default_lang.as_str(), &self.supported_languages)
200+
.and_then(|path_with_lang| {
201+
let full_path = self.base_path.join(path_with_lang.as_ref());
202+
let library = self.library.read().unwrap();
203+
204+
match library.sections.get(&full_path) {
205+
Some(s) => {
206+
if metadata_only {
207+
Ok(to_value(s.serialize_basic(&library)).unwrap())
208+
} else {
209+
Ok(to_value(s.serialize(&library)).unwrap())
210+
}
211+
}
212+
None => match lang {
213+
Some(lang_code) => Err(format!(
214+
"Section `{}` not found for language `{}`.",
215+
path, lang_code
216+
)
217+
.into()),
218+
None => Err(format!("Section `{}` not found.", path).into()),
219+
},
131220
}
132-
}
133-
None => Err(format!("Section `{}` not found.", path).into()),
134-
}
221+
})
135222
}
136223
}
137224

@@ -273,7 +360,148 @@ impl TeraFn for GetTaxonomyTerm {
273360
mod tests {
274361
use super::*;
275362
use config::{Config, TaxonomyConfig};
276-
use content::TaxonomyTerm;
363+
use content::{FileInfo, Library, Page, Section, SortBy, TaxonomyTerm};
364+
use std::path::Path;
365+
use std::sync::{Arc, RwLock};
366+
367+
fn create_page(title: &str, file_path: &str, lang: &str) -> Page {
368+
let mut page = Page { lang: lang.to_owned(), ..Page::default() };
369+
page.file = FileInfo::new_page(
370+
Path::new(format!("/test/base/path/{}", file_path).as_str()),
371+
&PathBuf::new(),
372+
);
373+
page.meta.title = Some(title.to_string());
374+
page.meta.weight = Some(1);
375+
page.file.find_language("en", &["fr"]).unwrap();
376+
page
377+
}
378+
379+
#[test]
380+
fn can_get_page() {
381+
let mut library = Library::default();
382+
let pages = vec![
383+
("Homepage", "content/homepage.md", "en"),
384+
("Page D'Accueil", "content/homepage.fr.md", "fr"),
385+
("Blog", "content/blog.md", "en"),
386+
("Wiki", "content/wiki.md", "en"),
387+
("Wiki", "content/wiki.fr.md", "fr"),
388+
("Recipes", "content/wiki/recipes.md", "en"),
389+
("Recettes", "content/wiki/recipes.fr.md", "fr"),
390+
("Programming", "content/wiki/programming.md", "en"),
391+
("La Programmation", "content/wiki/programming.fr.md", "fr"),
392+
("Novels", "content/novels.md", "en"),
393+
("Des Romans", "content/novels.fr.md", "fr"),
394+
];
395+
for (t, f, l) in pages.clone() {
396+
library.insert_page(create_page(t, f, l));
397+
}
398+
let base_path = "/test/base/path".into();
399+
let lang_list = vec!["en".to_string(), "fr".to_string()];
400+
401+
let static_fn =
402+
GetPage::new(base_path, "en", Arc::new(lang_list), Arc::new(RwLock::new(library)));
403+
404+
// Find with lang argument
405+
let mut args = HashMap::new();
406+
args.insert("path".to_string(), to_value("wiki/recipes.md").unwrap());
407+
args.insert("lang".to_string(), to_value("fr").unwrap());
408+
let res = static_fn.call(&args).unwrap();
409+
let res_obj = res.as_object().unwrap();
410+
assert_eq!(res_obj["title"], to_value("Recettes").unwrap());
411+
412+
// Find with lang in path for legacy support
413+
args = HashMap::new();
414+
args.insert("path".to_string(), to_value("wiki/recipes.fr.md").unwrap());
415+
let res = static_fn.call(&args).unwrap();
416+
let res_obj = res.as_object().unwrap();
417+
assert_eq!(res_obj["title"], to_value("Recettes").unwrap());
418+
419+
// Find with default lang
420+
args = HashMap::new();
421+
args.insert("path".to_string(), to_value("wiki/recipes.md").unwrap());
422+
let res = static_fn.call(&args).unwrap();
423+
let res_obj = res.as_object().unwrap();
424+
assert_eq!(res_obj["title"], to_value("Recipes").unwrap());
425+
426+
// Find with default lang when default lang passed
427+
args = HashMap::new();
428+
args.insert("path".to_string(), to_value("wiki/recipes.md").unwrap());
429+
args.insert("lang".to_string(), to_value("en").unwrap());
430+
let res = static_fn.call(&args).unwrap();
431+
let res_obj = res.as_object().unwrap();
432+
assert_eq!(res_obj["title"], to_value("Recipes").unwrap());
433+
}
434+
435+
fn create_section(title: &str, file_path: &str, lang: &str) -> Section {
436+
let mut section = Section { lang: lang.to_owned(), ..Section::default() };
437+
section.file = FileInfo::new_section(
438+
Path::new(format!("/test/base/path/{}", file_path).as_str()),
439+
&PathBuf::new(),
440+
);
441+
section.meta.title = Some(title.to_string());
442+
section.meta.weight = 1;
443+
section.meta.transparent = false;
444+
section.meta.sort_by = SortBy::None;
445+
section.meta.page_template = Some("new_page.html".to_owned());
446+
section.file.find_language("en", &["fr"]).unwrap();
447+
section
448+
}
449+
450+
#[test]
451+
fn can_get_section() {
452+
let mut library = Library::default();
453+
let sections = vec![
454+
("Homepage", "content/_index.md", "en"),
455+
("Page D'Accueil", "content/_index.fr.md", "fr"),
456+
("Blog", "content/blog/_index.md", "en"),
457+
("Wiki", "content/wiki/_index.md", "en"),
458+
("Wiki", "content/wiki/_index.fr.md", "fr"),
459+
("Recipes", "content/wiki/recipes/_index.md", "en"),
460+
("Recettes", "content/wiki/recipes/_index.fr.md", "fr"),
461+
("Programming", "content/wiki/programming/_index.md", "en"),
462+
("La Programmation", "content/wiki/programming/_index.fr.md", "fr"),
463+
("Novels", "content/novels/_index.md", "en"),
464+
("Des Romans", "content/novels/_index.fr.md", "fr"),
465+
];
466+
for (t, f, l) in sections.clone() {
467+
library.insert_section(create_section(t, f, l));
468+
}
469+
let base_path = "/test/base/path".into();
470+
let lang_list = vec!["en".to_string(), "fr".to_string()];
471+
472+
let static_fn =
473+
GetSection::new(base_path, "en", Arc::new(lang_list), Arc::new(RwLock::new(library)));
474+
475+
// Find with lang argument
476+
let mut args = HashMap::new();
477+
args.insert("path".to_string(), to_value("wiki/recipes/_index.md").unwrap());
478+
args.insert("lang".to_string(), to_value("fr").unwrap());
479+
let res = static_fn.call(&args).unwrap();
480+
let res_obj = res.as_object().unwrap();
481+
assert_eq!(res_obj["title"], to_value("Recettes").unwrap());
482+
483+
// Find with lang in path for legacy support
484+
args = HashMap::new();
485+
args.insert("path".to_string(), to_value("wiki/recipes/_index.fr.md").unwrap());
486+
let res = static_fn.call(&args).unwrap();
487+
let res_obj = res.as_object().unwrap();
488+
assert_eq!(res_obj["title"], to_value("Recettes").unwrap());
489+
490+
// Find with default lang
491+
args = HashMap::new();
492+
args.insert("path".to_string(), to_value("wiki/recipes/_index.md").unwrap());
493+
let res = static_fn.call(&args).unwrap();
494+
let res_obj = res.as_object().unwrap();
495+
assert_eq!(res_obj["title"], to_value("Recipes").unwrap());
496+
497+
// Find with default lang when default lang passed
498+
args = HashMap::new();
499+
args.insert("path".to_string(), to_value("wiki/recipes/_index.md").unwrap());
500+
args.insert("lang".to_string(), to_value("en").unwrap());
501+
let res = static_fn.call(&args).unwrap();
502+
let res_obj = res.as_object().unwrap();
503+
assert_eq!(res_obj["title"], to_value("Recipes").unwrap());
504+
}
277505

278506
#[test]
279507
fn can_get_taxonomy() {

docs/content/documentation/templates/overview.md

+24
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,18 @@ Takes a path to an `.md` file and returns the associated page. The base path is
141141
{% set page = get_page(path="blog/page2.md") %}
142142
```
143143

144+
If selecting a specific language for the page, you can pass `lang` with the language code to the function:
145+
146+
```jinja2
147+
{% set page = get_page(path="blog/page2.md", lang="fr") %}
148+
149+
{# If "fr" is the default language, this is equivalent to #}
150+
{% set page = get_page(path="blog/page2.md") %}
151+
152+
{# If "fr" is not the default language, this is equivalent to #}
153+
{% set page = get_page(path="blog/page2.fr.md") %}
154+
```
155+
144156
### `get_section`
145157
Takes a path to an `_index.md` file and returns the associated section. The base path is the `content` directory.
146158

@@ -154,6 +166,18 @@ If you only need the metadata of the section, you can pass `metadata_only=true`
154166
{% set section = get_section(path="blog/_index.md", metadata_only=true) %}
155167
```
156168

169+
If selecting a specific language for the section, you can pass `lang` with the language code to the function:
170+
171+
```jinja2
172+
{% set section = get_section(path="blog/_index.md", lang="fr") %}
173+
174+
{# If "fr" is the default language, this is equivalent to #}
175+
{% set section = get_section(path="blog/_index.md") %}
176+
177+
{# If "fr" is not the default language, this is equivalent to #}
178+
{% set section = get_section(path="blog/_index.fr.md") %}
179+
```
180+
157181
### `get_taxonomy_url`
158182
Gets the permalink for the taxonomy item found.
159183

0 commit comments

Comments
 (0)