[feature] Shortcodes and anchor-link.html can access lang context (#1609)

* Pass lang to shortcodes context

* Add tests for lang in shortcodes

* Lang is passed to anchor-link.html template

* Document passing lang to shortcodes/anchor-link.html
Add a test to make sure lang can be overriden by passing an explicit argument to the shortcode,
for usage in the markdown filter where the language context is not available.

* Update docs for more information on shortcodes+i18n+markdown() filter

Co-authored-by: southerntofu <southerntofu@thunix.net>
This commit is contained in:
southerntofu 2021-09-04 06:10:33 +00:00 committed by GitHub
parent eceb1bd79d
commit 84b75f9725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 123 additions and 4 deletions

View File

@ -14,6 +14,7 @@ pub struct RenderContext<'a> {
pub current_page_permalink: &'a str, pub current_page_permalink: &'a str,
pub permalinks: Cow<'a, HashMap<String, String>>, pub permalinks: Cow<'a, HashMap<String, String>>,
pub insert_anchor: InsertAnchor, pub insert_anchor: InsertAnchor,
pub lang: &'a str,
} }
impl<'a> RenderContext<'a> { impl<'a> RenderContext<'a> {
@ -34,10 +35,13 @@ impl<'a> RenderContext<'a> {
permalinks: Cow::Borrowed(permalinks), permalinks: Cow::Borrowed(permalinks),
insert_anchor, insert_anchor,
config, config,
lang,
} }
} }
// In use in the markdown filter // In use in the markdown filter
// NOTE: This RenderContext is not i18n-aware, see MarkdownFilter::filter for details
// If this function is ever used outside of MarkdownFilter, take this into consideration
pub fn from_config(config: &'a Config) -> RenderContext<'a> { pub fn from_config(config: &'a Config) -> RenderContext<'a> {
Self { Self {
tera: Cow::Owned(Tera::default()), tera: Cow::Owned(Tera::default()),
@ -46,6 +50,7 @@ impl<'a> RenderContext<'a> {
permalinks: Cow::Owned(HashMap::new()), permalinks: Cow::Owned(HashMap::new()),
insert_anchor: InsertAnchor::None, insert_anchor: InsertAnchor::None,
config, config,
lang: &config.default_language,
} }
} }
} }

View File

@ -342,6 +342,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
let mut c = tera::Context::new(); let mut c = tera::Context::new();
c.insert("id", &id); c.insert("id", &id);
c.insert("level", &heading_ref.level); c.insert("level", &heading_ref.level);
c.insert("lang", &context.lang);
let anchor_link = utils::templates::render_template( let anchor_link = utils::templates::render_template(
ANCHOR_LINK_TEMPLATE, ANCHOR_LINK_TEMPLATE,

View File

@ -107,6 +107,11 @@ fn render_shortcode(
body: Option<&str>, body: Option<&str>,
) -> Result<String> { ) -> Result<String> {
let mut tera_context = Context::new(); let mut tera_context = Context::new();
// nth and lang are inserted before passed arguments, so that they can be overriden explicitly
tera_context.insert("nth", &invocation_count);
tera_context.insert("lang", &context.lang);
for (key, value) in args.iter() { for (key, value) in args.iter() {
tera_context.insert(key, value); tera_context.insert(key, value);
} }
@ -114,7 +119,7 @@ fn render_shortcode(
// Trimming right to avoid most shortcodes with bodies ending up with a HTML new line // Trimming right to avoid most shortcodes with bodies ending up with a HTML new line
tera_context.insert("body", b.trim_end()); tera_context.insert("body", b.trim_end());
} }
tera_context.insert("nth", &invocation_count);
tera_context.extend(context.tera_context.clone()); tera_context.extend(context.tera_context.clone());
let mut template_name = format!("shortcodes/{}.md", name); let mut template_name = format!("shortcodes/{}.md", name);

View File

@ -181,6 +181,47 @@ fn can_render_shortcode_with_markdown_char_in_args_value() {
} }
} }
#[test]
fn can_render_html_shortcode_with_lang() {
let permalinks_ctx = HashMap::new();
let config = Config::default_for_test();
let mut tera = Tera::default();
tera.extend(&ZOLA_TERA).unwrap();
tera.add_raw_template("shortcodes/i18nshortcode.html", "{{ lang }}").unwrap();
let context = RenderContext::new(
&tera,
&config,
&config.default_language,
"",
&permalinks_ctx,
InsertAnchor::None,
);
let res = render_content("a{{ i18nshortcode() }}a", &context).unwrap();
assert_eq!(res.body, "<p>aena</p>\n");
}
#[test]
fn can_render_md_shortcode_with_lang() {
let permalinks_ctx = HashMap::new();
let config = Config::default_for_test();
let mut tera = Tera::default();
tera.extend(&ZOLA_TERA).unwrap();
tera.add_raw_template("shortcodes/i18nshortcode.md", "![Book cover in {{ lang }}](cover.{{ lang }}.png)").unwrap();
let context = RenderContext::new(
&tera,
&config,
&config.default_language,
"",
&permalinks_ctx,
InsertAnchor::None,
);
let res = render_content("{{ i18nshortcode() }}", &context).unwrap();
assert_eq!(res.body, "<p><img src=\"cover.en.png\" alt=\"Book cover in en\" /></p>\n");
}
#[test] #[test]
fn can_render_body_shortcode_with_markdown_char_in_name() { fn can_render_body_shortcode_with_markdown_char_in_name() {
let permalinks_ctx = HashMap::new(); let permalinks_ctx = HashMap::new();
@ -689,6 +730,28 @@ fn can_insert_anchor_with_other_special_chars() {
); );
} }
#[test]
fn can_insert_anchor_with_lang() {
let mut tera = Tera::default();
tera.extend(&ZOLA_TERA).unwrap();
tera.add_raw_template("anchor-link.html", "({{ lang }})").unwrap();
let permalinks_ctx = HashMap::new();
let config = Config::default_for_test();
let context = RenderContext::new(
&tera,
&config,
&config.default_language,
"",
&permalinks_ctx,
InsertAnchor::Right,
);
let res = render_content("# Hello", &context).unwrap();
assert_eq!(
res.body,
"<h1 id=\"hello\">Hello(en)</h1>\n"
);
}
#[test] #[test]
fn can_make_toc() { fn can_make_toc() {
let permalinks_ctx = HashMap::new(); let permalinks_ctx = HashMap::new();

View File

@ -33,6 +33,10 @@ impl MarkdownFilter {
impl TeraFilter for MarkdownFilter { impl TeraFilter for MarkdownFilter {
fn filter(&self, value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> { fn filter(&self, value: &Value, args: &HashMap<String, Value>) -> TeraResult<Value> {
// NOTE: RenderContext below is not aware of the current language
// However, it should not be a problem because the surrounding tera
// template has language context, and will most likely call a piece of
// markdown respecting language preferences.
let mut context = RenderContext::from_config(&self.config); let mut context = RenderContext::from_config(&self.config);
context.permalinks = Cow::Borrowed(&self.permalinks); context.permalinks = Cow::Borrowed(&self.permalinks);
context.tera = Cow::Borrowed(&self.tera); context.tera = Cow::Borrowed(&self.tera);
@ -123,6 +127,22 @@ mod tests {
assert_eq!(result.unwrap(), to_value(&"<h1 id=\"hey\">Hey</h1>\n").unwrap()); assert_eq!(result.unwrap(), to_value(&"<h1 id=\"hey\">Hey</h1>\n").unwrap());
} }
#[test]
fn markdown_filter_override_lang() {
// We're checking that we can use a workaround to explicitly provide `lang` in markdown filter from tera,
// because otherwise markdown filter shortcodes are not aware of the current language
// NOTE: This should also work for `nth` although i don't see a reason to do that
let args = HashMap::new();
let config = Config::default();
let permalinks = HashMap::new();
let mut tera = super::load_tera(&PathBuf::new(), &config).map_err(tera::Error::msg).unwrap();
tera.add_raw_template("shortcodes/explicitlang.html", "a{{ lang }}a").unwrap();
let filter = MarkdownFilter { config, permalinks, tera };
let result = filter.filter(&to_value(&"{{ explicitlang(lang='jp') }}").unwrap(), &args);
assert!(result.is_ok());
assert_eq!(result.unwrap(), to_value(&"ajpa").unwrap());
}
#[test] #[test]
fn markdown_filter_inline() { fn markdown_filter_inline() {
let mut args = HashMap::new(); let mut args = HashMap::new();

View File

@ -39,11 +39,12 @@ This option is set at the section level: the `insert_anchor_links` variable on t
The default template is very basic and will need CSS tweaks in your project to look decent. The default template is very basic and will need CSS tweaks in your project to look decent.
If you want to change the anchor template, it can be easily overwritten by If you want to change the anchor template, it can be easily overwritten by
creating an `anchor-link.html` file in the `templates` directory. creating an `anchor-link.html` file in the `templates` directory. [Here](https://github.com/getzola/zola/blob/master/components/templates/src/builtins/anchor-link.html) you can find the default template.
The anchor link template has the following variables: The anchor link template has the following variables:
- `id`: the heading's id after applying the rules defined by `slugify.anchors` - `id`: the heading's id after applying the rules defined by `slugify.anchors`
- `lang`: the current language, unless called from the `markdown` template filter, in which case it will always be `en`
- `level`: the heading level (between 1 and 6) - `level`: the heading level (between 1 and 6)
## Internal links ## Internal links

View File

@ -134,10 +134,19 @@ If you want to have some content that looks like a shortcode but not have Zola t
you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape
anything else until the closing tag. anything else until the closing tag.
## Shortcode context
Every shortcode can access some variables, beyond what you explicitly passed as parameter. These variables are explained in the following subsections:
- invocation count (`nth`)
- current language (`lang`), unless called from the `markdown` template filter (in which case it will always be the same value as `default_language` in configuration, or `en` when it is unset)
When one of these variables conflict with a variable passed as argument, the argument value will be used.
### Invocation Count ### Invocation Count
Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has
been invoked in a Markdown file. Given a shortcode `true_statement.html` template: been invoked in the current Markdown file. Given a shortcode `true_statement.html` template:
```jinja2 ```jinja2
<p id="number{{ nth }}">{{ value }} is equal to {{ nth }}.</p> <p id="number{{ nth }}">{{ value }} is equal to {{ nth }}.</p>
@ -152,6 +161,18 @@ It could be used in our Markdown as follows:
This is useful when implementing custom markup for features such as sidenotes or end notes. This is useful when implementing custom markup for features such as sidenotes or end notes.
### Current language
**NOTE:** When calling a shortcode from within the `markdown` template filter, the `lang` variable will always be `en`. If you feel like you need that, please consider using template macros instead. If you really need that, you can rewrite your Markdown content to pass `lang` as argument to the shortcode.
Every shortcode can access the current language in the `lang` variable in the context. This is useful for presenting/filtering information in a shortcode depending in a per-language manner. For example, to display a per-language book cover for the current page in a shortcode called `bookcover.md`:
```jinja2
![Book cover in {{ lang }}](cover.{{ lang }}.png)
```
You can then use it in your Markdown like so: `{{/* bookcover() */}}`
## Built-in shortcodes ## Built-in shortcodes
Zola comes with a few built-in shortcodes. If you want to override a default shortcode template, Zola comes with a few built-in shortcodes. If you want to override a default shortcode template,

View File

@ -64,7 +64,10 @@ Zola adds a few filters in addition to [those](https://tera.netlify.com/docs/#fi
in Tera. in Tera.
### markdown ### markdown
Converts the given variable to HTML using Markdown. Please note that shortcodes evaluated by this filter cannot access the current rendering context. `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building. See [this discussion](https://github.com/getzola/zola/pull/1358). Converts the given variable to HTML using Markdown. There are a few differences compared to page/section Markdown rendering:
- shortcodes evaluated by this filter cannot access the current rendering context: `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building (see [this discussion](https://github.com/getzola/zola/pull/1358))
- `lang` in shortcodes will always be equal to the site's `default_lang` (or `en` otherwise) ; it should not be a problem, but if it is in most cases, but if you need to use language-aware shortcodes in this filter, please refer to the [Shortcode context](@/documentation/content/shortcodes.md#shortcode-context) section of the docs.
By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can
pass `true` to the inline argument: pass `true` to the inline argument: