Localize all of the template text

This commit is contained in:
object-Object 2023-09-10 01:19:49 -04:00
parent 790521df05
commit 504ec5b48c
21 changed files with 269 additions and 88 deletions

View file

@ -16,6 +16,7 @@
"editor.rulers": [120], "editor.rulers": [120],
}, },
"files.associations": { "files.associations": {
"*.js.jinja": "javascript" "*.js.jinja": "javascript",
"*.css.jinja": "css",
} }
} }

View file

@ -40,10 +40,8 @@ packages = [
] ]
[template.args] [template.args]
title = "Hex Book"
mod_name = "Hex Casting" mod_name = "Hex Casting"
author = "petrak@, Alwinfy" author = "petrak@, Alwinfy"
description = "The Hex Book, all in one place."
icon_href = "logo.png" icon_href = "logo.png"
show_landing_text = true show_landing_text = true

View file

@ -1,4 +1,63 @@
{ {
hexdoc: {
hexcasting: {
title: "Hex Book",
description: "The Hex Book, all in one place.",
},
welcome: {
"1": "This is the online version of the %s documentation.",
"2": "Embedded images and patterns are included, but not crafting recipes or items. \
There's an in-game book for those.",
"3": "Additionally, this is built from the latest code on GitHub. \
It may describe $(bold)newer/$ features that you may not necessarily have, \
even on the latest Modrinth/CurseForge version!",
"4": "$(bold)Entries which are blurred are spoilers/$. \
Click to reveal them, but be aware that they may spoil endgame progression. \
Alternatively, click $(l:?nospoiler)here/$ to get a version with all spoilers showing.",
"old_version": "$(italic)The past is a foreign country; they do things differently there./$",
},
toc: {
// use the span tag to make everything after it wrap with the toc button
// or put it at the very end to make the button wrap by itself
// either way, do NOT remove it entirely
title: "Table of {{ '<span class=\"nobr\">'|safe }}Contents",
toggle_all: "Toggle all",
},
pattern: {
show: "Click to show spell",
hide: "Click to hide spell",
multiplier: "${speedScale()}x",
paused: "Paused",
not_supported: "Your browser does not support visualizing patterns. Pattern code: %s",
},
pages: {
patchouli: {
crafting: {
description: "Depicted in the book: The crafting recipe for the",
separator: " and ",
},
},
hexcasting: {
crafting_multi: {
description: "Depicted in the book: Several crafting recipes, for the",
separator: ", ",
},
brainsweep: {
description: "Depicted in the book: A mind-flaying recipe producing the",
separator: "", // this value intentionally left blank
},
},
},
},
key: { key: {
use: "Right Click", use: "Right Click",
sneak: "Left Shift", sneak: "Left Shift",

View file

@ -42,7 +42,7 @@
{# shows the names of all the recipe results in a list of recipes #} {# shows the names of all the recipe results in a list of recipes #}
{% macro recipe_block(recipes, result_attribute, description, separator) -%} {% macro recipe_block(recipes, result_attribute, description, separator) -%}
<blockquote class="crafting-info"> <blockquote class="crafting-info">
Depicted in the book: {{ description }} {{ {{ description }} {{
recipes recipes
|map(attribute="result." ~ result_attribute) |map(attribute="result." ~ result_attribute)
|map("hexdoc_wrap", "code") |map("hexdoc_wrap", "code")

View file

@ -14,7 +14,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="{{ props.url }}">{{ title }}</a> <a class="navbar-brand" href="{{ props.url }}">{{ _("hexdoc."~props.modid~".title") }}</a>
</div> </div>
<!-- content --> <!-- content -->

View file

@ -4,11 +4,11 @@
<div id="table-of-contents"></div> <div id="table-of-contents"></div>
<nav class="toc-container"> <nav class="toc-container">
<h2 class="page-header"> <h2 class="page-header">
Table of <span class="nobr">Contents<a {{ _("hexdoc.toc.title") }}<a
href="javascript:void(0)" href="javascript:void(0)"
class="permalink toggle-link small" class="permalink toggle-link small"
data-target="toc-category" data-target="toc-category"
title="Toggle all" title="{{ _('hexdoc.toc.toggle_all') }}"
><i class="bi bi-list-nested"></i></a>{{ macros.permalink("table-of-contents", "toc-permalink") }}</span> ><i class="bi bi-list-nested"></i></a>{{ macros.permalink("table-of-contents", "toc-permalink") }}</span>
</h2> </h2>

View file

@ -1,26 +1,16 @@
<div class="container" style="margin-top: 3em;"> <div class="container" style="margin-top: 3em;">
<blockquote> <blockquote>
<h1>This is the online version of the {{ mod_name }} documentation.</h1> <h1>{{ _f("hexdoc.welcome.1")|format(mod_name) }}</h1>
<p> <p>{{ _f("hexdoc.welcome.2") }}</p>
Embedded images and patterns are included, but not crafting recipes or items. There's an in-game book for those.
</p>
{% if is_bleeding_edge %} {% if is_bleeding_edge %}
<p> <p>{{ _f("hexdoc.welcome.3") }}</p>
Additionally, this is built from the latest code on GitHub.
It may describe <b>newer</b> features that you may not necessarily have, even on the latest CurseForge version!
</p>
{% endif %} {% endif %}
<p>{{ _f("hexdoc.welcome.4") }}</p>
<p>
<b>Entries which are blurred are spoilers</b>. Click to reveal them, but be aware that they may spoil endgame
progression. Alternatively, click <a href="?nospoiler">here</a> to get a version with all spoilers showing.
</p>
{# conditionally revealed using js #} {# conditionally revealed using js #}
<span id="old-version-notice" class="hidden"> <span id="old-version-notice" class="hidden">
<br /> <br />
<i>The past is a foreign country; they do things differently there.</i> {{ _f("hexdoc.welcome.old_version") }}
</span> </span>
</blockquote> </blockquote>
</div> </div>

View file

@ -26,11 +26,11 @@ details[open] summary.collapse-spell {
} }
details .collapse-spell::before { details .collapse-spell::before {
content: "Click to show spell"; content: "{{ _('hexdoc.pattern.show') }}";
} }
details[open] .collapse-spell::before { details[open] .collapse-spell::before {
content: "Click to hide spell"; content: "{{ _('hexdoc.pattern.hide') }}";
} }
blockquote.crafting-info { blockquote.crafting-info {

View file

@ -1,19 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>{{ title }}</title> <title>{{ _("hexdoc."~props.modid~".title") }}</title>
<link rel="icon" href="{{ icon_href }}"> <link rel="icon" href="{{ icon_href }}">
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="{{ description }}" /> <meta name="description" content="{{ _('hexdoc.'~props.modid~'.description') }}" />
<meta name="author" content="{{ author }}" /> <meta name="author" content="{{ author }}" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:title" content="{{ title }}" /> <meta property="og:title" content="{{ _('hexdoc.'~props.modid~'.title') }}" />
<meta property="og:image" content="{{ page_url }}/{{ icon_href }}" /> <meta property="og:image" content="{{ page_url }}/{{ icon_href }}" />
<meta property="og:url" content="{{ page_url }}" /> <meta property="og:url" content="{{ page_url }}" />
<meta property="og:description" content="{{ description }}" /> <meta property="og:description" content="{{ _('hexdoc.'~props.modid~'.description') }}" />
<meta property="og:site_name" content="{{ mod_name }}" /> <meta property="og:site_name" content="{{ mod_name }}" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css" <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
@ -27,7 +27,7 @@
<style> <style>
{% filter indent(6) %} {% filter indent(6) %}
{%+ include_raw "main.css" %} {%+ include "main.css.jinja" %}
{% endfilter %} {% endfilter %}
</style> </style>

View file

@ -212,7 +212,9 @@ function initializeElem(canvas) {
})`; })`;
context.font = `${pauseScale}px sans-serif`; context.font = `${pauseScale}px sans-serif`;
context.fillText( context.fillText(
speedScale() ? speedScale() + "x" : "Paused", // these variables are filled by Jinja
// slightly scuffed, but it works for now
speedScale() ? `{{ _('hexdoc.pattern.multiplier') }}` : "{{ _('hexdoc.pattern.paused') }}",
0.2 * scale, 0.2 * scale,
canvas.height - 0.2 * scale canvas.height - 0.2 * scale
); );

View file

@ -1,7 +1,12 @@
{% extends "pages/patchouli/page.html.jinja" %} {% extends "pages/patchouli/page.html.jinja" %}
{% include "common/macros.html.jinja" %} {% include "common/macros.html.jinja" with context %}
{% block body %} {% block body %}
{{ macros.recipe_block([page.recipe], "name", "A mind-flaying recipe producing the", "") }} {{ macros.recipe_block(
[page.recipe],
"name",
_("hexdoc.pages.hexcasting.brainsweep.description"),
_("hexdoc.pages.hexcasting.brainsweep.separator"),
) }}
{{ page.text|hexdoc_block }} {{ page.text|hexdoc_block }}
{% endblock body %} {% endblock body %}

View file

@ -1,7 +1,12 @@
{% extends "pages/patchouli/text.html.jinja" %} {% extends "pages/patchouli/text.html.jinja" %}
{% include "common/macros.html.jinja" %} {% include "common/macros.html.jinja" with context %}
{% block inner_body %} {% block inner_body %}
{{ macros.recipe_block(page.recipes, "item", "Several crafting recipes, for the", ", ") }} {{ macros.recipe_block(
page.recipes,
"item",
_("hexdoc.pages.hexcasting.crafting_multi.description"),
_("hexdoc.pages.hexcasting.crafting_multi.separator"),
) }}
{{ super() }} {{ super() }}
{% endblock inner_body %} {% endblock inner_body %}

View file

@ -11,7 +11,7 @@
data-string="{{ pattern.signature }}" data-string="{{ pattern.signature }}"
data-start="{{ pattern.startdir.name.lower() }}" data-start="{{ pattern.startdir.name.lower() }}"
data-per-world="{{ pattern.is_per_world }}" data-per-world="{{ pattern.is_per_world }}"
>Your browser does not support visualizing patterns. Pattern code: {{ pattern.signature }}</canvas> >{{ _("hexdoc.pattern.not_supported")|format(pattern.signature) }}</canvas>
{% endfor %} {% endfor %}
</details> </details>
{{ super() }} {{ super() }}

View file

@ -2,6 +2,11 @@
{% include "common/macros.html.jinja" %} {% include "common/macros.html.jinja" %}
{% block inner_body %} {% block inner_body %}
{{ macros.recipe_block(page.recipes, "item", "The crafting recipe for the", " and ") }} {{ macros.recipe_block(
page.recipes,
"item",
_("hexdoc.pages.patchouli.crafting.description"),
_("hexdoc.pages.patchouli.crafting.separator"),
) }}
{{ super() }} {{ super() }}
{% endblock inner_body %} {% endblock inner_body %}

View file

@ -124,6 +124,9 @@ class Book(HexdocModel):
return self return self
context = cast_or_raise(info.context, BookContext) context = cast_or_raise(info.context, BookContext)
# make the macros accessible when rendering the template
self.macros |= context.macros
self._link_bases: dict[tuple[ResourceLocation, str | None], str] = {} self._link_bases: dict[tuple[ResourceLocation, str | None], str] = {}
# load categories # load categories

View file

@ -13,8 +13,7 @@ from pydantic.dataclasses import dataclass
from pydantic.functional_validators import ModelWrapValidatorHandler from pydantic.functional_validators import ModelWrapValidatorHandler
from hexdoc.minecraft import LocalizedStr from hexdoc.minecraft import LocalizedStr
from hexdoc.minecraft.i18n import I18nContext from hexdoc.minecraft.i18n import I18n, I18nContext
from hexdoc.patchouli.text.html import HTMLElement, HTMLStream
from hexdoc.utils import DEFAULT_CONFIG, HexdocModel from hexdoc.utils import DEFAULT_CONFIG, HexdocModel
from hexdoc.utils.deserialize import cast_or_raise from hexdoc.utils.deserialize import cast_or_raise
from hexdoc.utils.resource import ResourceLocation from hexdoc.utils.resource import ResourceLocation
@ -156,7 +155,12 @@ class Style(ABC, HexdocModel, frozen=True):
type: CommandStyleType | FunctionStyleType | SpecialStyleType type: CommandStyleType | FunctionStyleType | SpecialStyleType
@staticmethod @staticmethod
def parse(style_str: str, context: FormattingContext) -> Style | _CloseTag | str: def parse(
style_str: str,
book_id: ResourceLocation,
i18n: I18n,
is_0_black: bool,
) -> Style | _CloseTag | str:
# direct text replacements # direct text replacements
if style_str in _REPLACEMENTS: if style_str in _REPLACEMENTS:
return _REPLACEMENTS[style_str] return _REPLACEMENTS[style_str]
@ -170,7 +174,7 @@ class Style(ABC, HexdocModel, frozen=True):
return CommandStyle(type=style_type) return CommandStyle(type=style_type)
# reset color, but only if 0 is considered reset instead of black # reset color, but only if 0 is considered reset instead of black
if not context.props.is_0_black and style_str == "0": if style_str == "0" and not is_0_black:
return _CloseTag(type=SpecialStyleType.color) return _CloseTag(type=SpecialStyleType.color)
# preset colors # preset colors
@ -187,11 +191,11 @@ class Style(ABC, HexdocModel, frozen=True):
# keys # keys
if name == "k": if name == "k":
return str(context.i18n.localize_key(value)) return str(i18n.localize_key(value))
# links # links
if name == SpecialStyleType.link.value: if name == SpecialStyleType.link.value:
return LinkStyle(value=_format_href(value, context.book_id)) return LinkStyle(value=_format_href(value, book_id))
# all the other functions # all the other functions
if style_type := FunctionStyleType.get(name): if style_type := FunctionStyleType.get(name):
@ -224,7 +228,8 @@ def is_external_link(value: str) -> bool:
def _format_href(value: str, book_id: ResourceLocation) -> str | BookLink: def _format_href(value: str, book_id: ResourceLocation) -> str | BookLink:
if is_external_link(value): # TODO: kinda hacky, BookLink should *probably* support query params
if value.startswith("?") or is_external_link(value):
return value return value
return BookLink.from_str(value, book_id) return BookLink.from_str(value, book_id)
@ -326,13 +331,21 @@ class FormatTree:
children: list[FormatTree | str] # this can't be Self, it breaks Pydantic children: list[FormatTree | str] # this can't be Self, it breaks Pydantic
@classmethod @classmethod
def format(cls, string: str, context: FormattingContext) -> Self: def format(
cls,
string: str,
*,
book_id: ResourceLocation,
i18n: I18n,
macros: dict[str, str],
is_0_black: bool,
) -> Self:
# resolve macros # resolve macros
# this could use ahocorasick, but it works fine for now # this could use ahocorasick, but it works fine for now
old_string = None old_string = None
while old_string != string: while old_string != string:
old_string = string old_string = string
for macro, replace in context.macros.items(): for macro, replace in macros.items():
string = string.replace(macro, replace) string = string.replace(macro, replace)
# lex out parsed styles # lex out parsed styles
@ -347,7 +360,7 @@ class FormatTree:
text_since_prev_style.append(leading_text) text_since_prev_style.append(leading_text)
last_end = match.end() last_end = match.end()
match Style.parse(match[1], context): match Style.parse(match[1], book_id, i18n, is_0_black):
case str(replacement): case str(replacement):
# str means "use this instead of the original value" # str means "use this instead of the original value"
text_since_prev_style.append(replacement) text_since_prev_style.append(replacement)
@ -404,11 +417,18 @@ class FormatTree:
): ):
if not info.context or isinstance(value, FormatTree): if not info.context or isinstance(value, FormatTree):
return handler(value) return handler(value)
context = cast_or_raise(info.context, FormattingContext)
context = cast_or_raise(info.context, FormattingContext)
if isinstance(value, str): if isinstance(value, str):
value = context.i18n.localize(value) value = context.i18n.localize(value)
return cls.format(value.value, context)
return cls.format(
value.value,
book_id=context.book_id,
i18n=context.i18n,
macros=context.macros,
is_0_black=context.props.is_0_black,
)
FormatTree._wrap_root FormatTree._wrap_root

View file

@ -1,3 +1,6 @@
# pyright: reportUnknownMemberType=false, reportUnknownArgumentType=false
# pyright: reportUnknownLambdaType=false
import io import io
import json import json
import logging import logging
@ -24,7 +27,12 @@ from hexdoc.patchouli import Book
from hexdoc.plugin import PluginManager from hexdoc.plugin import PluginManager
from hexdoc.utils import HexdocModel, ModResourceLoader, Properties from hexdoc.utils import HexdocModel, ModResourceLoader, Properties
from hexdoc.utils.deserialize import cast_or_raise from hexdoc.utils.deserialize import cast_or_raise
from hexdoc.utils.jinja_extensions import IncludeRawExtension, hexdoc_block, hexdoc_wrap from hexdoc.utils.jinja_extensions import (
IncludeRawExtension,
hexdoc_block,
hexdoc_localize,
hexdoc_wrap,
)
from hexdoc.utils.path import write_to_path from hexdoc.utils.path import write_to_path
MARKER_NAME = ".sitemap-marker.json" MARKER_NAME = ".sitemap-marker.json"
@ -151,7 +159,7 @@ def main(args: Args | None = None) -> None:
# load everything # load everything
with ModResourceLoader.clean_and_load_all(props, pm) as loader: with ModResourceLoader.clean_and_load_all(props, pm) as loader:
books = dict[str, Book]() books = dict[str, tuple[Book, I18n]]()
if args.lang: if args.lang:
first_lang = args.lang first_lang = args.lang
@ -177,12 +185,15 @@ def main(args: Args | None = None) -> None:
# load one book with exporting enabled # load one book with exporting enabled
first_i18n = per_lang_i18n.pop(first_lang) first_i18n = per_lang_i18n.pop(first_lang)
books[first_lang] = load_hex_book(book_data, pm, loader, first_i18n) books[first_lang] = (
load_hex_book(book_data, pm, loader, first_i18n),
first_i18n,
)
# then load the rest with exporting disabled for efficiency # then load the rest with exporting disabled for efficiency
loader.export_dir = None loader.export_dir = None
for lang, i18n in per_lang_i18n.items(): for lang, i18n in per_lang_i18n.items():
books[lang] = load_hex_book(book_data, pm, loader, i18n) books[lang] = (load_hex_book(book_data, pm, loader, i18n), i18n)
if args.export_only: if args.export_only:
return return
@ -203,10 +214,10 @@ def main(args: Args | None = None) -> None:
IncludeRawExtension, IncludeRawExtension,
], ],
) )
env.filters |= { # pyright: ignore[reportGeneralTypeIssues]
env.filters |= { # type: ignore
"hexdoc_block": hexdoc_block, "hexdoc_block": hexdoc_block,
"hexdoc_wrap": hexdoc_wrap, "hexdoc_wrap": hexdoc_wrap,
"hexdoc_localize": hexdoc_localize,
} }
template = env.get_template(props.template.main) template = env.get_template(props.template.main)
@ -218,27 +229,53 @@ def main(args: Args | None = None) -> None:
shutil.rmtree(output_dir, ignore_errors=True) shutil.rmtree(output_dir, ignore_errors=True)
if args.update_latest: if args.update_latest:
render_books(props, books, template, output_dir, "latest") render_books(
props=props,
books=books,
template=template,
output_dir=output_dir,
allow_missing=args.allow_missing,
version="latest",
is_root=False,
)
if args.is_release: if args.is_release:
render_books(props, books, template, output_dir, version) render_books(
props=props,
books=books,
template=template,
output_dir=output_dir,
allow_missing=args.allow_missing,
version=version,
is_root=False,
)
# the default book should be the latest released version # the default book should be the latest released version
if args.update_latest and args.is_release: if args.update_latest and args.is_release:
render_books(props, books, template, output_dir, version, is_root=True) render_books(
props=props,
books=books,
template=template,
output_dir=output_dir,
allow_missing=args.allow_missing,
version=version,
is_root=True,
)
logger.info("Done.") logger.info("Done.")
def render_books( def render_books(
*,
props: Properties, props: Properties,
books: dict[str, Book], books: dict[str, tuple[Book, I18n]],
template: Template, template: Template,
output_dir: Path, output_dir: Path,
allow_missing: bool,
version: str, version: str,
is_root: bool = False, is_root: bool,
): ):
for lang, book in books.items(): for lang, (book, i18n) in books.items():
# /index.html # /index.html
# /lang/index.html # /lang/index.html
# /v/version/index.html # /v/version/index.html
@ -254,8 +291,8 @@ def render_books(
page_url = "/".join([props.url, *path.parts]) page_url = "/".join([props.url, *path.parts])
logging.getLogger(__name__).info(f"Rendering {output_dir}") logging.getLogger(__name__).info(f"Rendering {output_dir}")
docs = strip_empty_lines(
template.render( raw_docs = template.render(
**props.template.args, **props.template.args,
book=book, book=book,
props=props, props=props,
@ -263,8 +300,26 @@ def render_books(
version=version, version=version,
lang=lang, lang=lang,
is_bleeding_edge=version == "latest", is_bleeding_edge=version == "latest",
# i18n helper
_=lambda key: hexdoc_localize(
key,
do_format=False,
props=props,
book=book,
i18n=i18n,
allow_missing=allow_missing,
),
# i18n helper, but with patchi formatting
_f=lambda key: hexdoc_localize(
key,
do_format=True,
props=props,
book=book,
i18n=i18n,
allow_missing=allow_missing,
),
) )
) docs = strip_empty_lines(raw_docs)
write_to_path(output_dir / "index.html", docs) write_to_path(output_dir / "index.html", docs)
if props.template.static_dir: if props.template.static_dir:

View file

@ -6,11 +6,14 @@ from jinja2.parser import Parser
from jinja2.runtime import Context from jinja2.runtime import Context
from markupsafe import Markup from markupsafe import Markup
from hexdoc.minecraft import LocalizedStr from hexdoc.minecraft import I18n, LocalizedStr
from hexdoc.patchouli import FormatTree from hexdoc.patchouli import Book, FormatTree
from hexdoc.patchouli.book import Book from hexdoc.patchouli.book import Book
from hexdoc.patchouli.text import HTMLStream from hexdoc.patchouli.text import HTMLStream
from hexdoc.utils.deserialize import cast_or_raise from hexdoc.patchouli.text.formatting import FormatTree
from . import Properties
from .deserialize import cast_or_raise
# https://stackoverflow.com/a/64392515 # https://stackoverflow.com/a/64392515
@ -30,15 +33,16 @@ class IncludeRawExtension(Extension):
@pass_context @pass_context
def hexdoc_block(context: Context, value: Any) -> str: def hexdoc_block(context: Context | dict[{"book": Book}], value: Any) -> str:
try: try:
return _hexdoc_block(context, value) book = cast_or_raise(context["book"], Book)
return _hexdoc_block(book, value)
except Exception as e: except Exception as e:
e.add_note(f"Value:\n {value}") e.add_note(f"Value:\n {value}")
raise raise
def _hexdoc_block(context: Context, value: Any) -> str: def _hexdoc_block(book: Book, value: Any) -> str:
match value: match value:
case LocalizedStr() | str(): case LocalizedStr() | str():
# use Markup to tell Jinja not to escape this string for us # use Markup to tell Jinja not to escape this string for us
@ -46,11 +50,10 @@ def _hexdoc_block(context: Context, value: Any) -> str:
return Markup("<br />".join(Markup.escape(line) for line in lines)) return Markup("<br />".join(Markup.escape(line) for line in lines))
case FormatTree(): case FormatTree():
book = cast_or_raise(context["book"], Book)
with HTMLStream() as out: with HTMLStream() as out:
with value.style.element(out, book.link_bases): with value.style.element(out, book.link_bases):
for child in value.children: for child in value.children:
out.write(_hexdoc_block(context, child)) out.write(_hexdoc_block(book, child))
return Markup(out.getvalue()) return Markup(out.getvalue())
case None: case None:
@ -66,3 +69,30 @@ def hexdoc_wrap(value: str, *args: str):
else: else:
attributes = "" attributes = ""
return Markup(f"<{tag}{attributes}>{Markup.escape(value)}</{tag}>") return Markup(f"<{tag}{attributes}>{Markup.escape(value)}</{tag}>")
# aliased as _() and _f() at render time
def hexdoc_localize(
key: str,
*,
do_format: bool,
props: Properties,
book: Book,
i18n: I18n,
allow_missing: bool,
):
# get the localized value from i18n
localized = i18n.localize(key, allow_missing=allow_missing)
if not do_format:
return Markup(localized.value)
# construct a FormatTree from the localized value (to allow using patchi styles)
formatted = FormatTree.format(
localized.value,
book_id=book.id,
i18n=i18n,
macros=book.macros,
is_0_black=props.is_0_black,
)
return Markup(hexdoc_block({"book": book}, formatted))

View file

@ -46,10 +46,8 @@ packages = [
] ]
[template.args] [template.args]
title = "{{ cookiecutter.mod_name }} Book"
mod_name = "{{ cookiecutter.mod_name }}" mod_name = "{{ cookiecutter.mod_name }}"
author = "{{ cookiecutter.author }}" author = "{{ cookiecutter.author }}"
description = "The {{ cookiecutter.mod_name }} Book, all in one place."
icon_href = "icon.png" icon_href = "icon.png"
show_landing_text = false show_landing_text = false

View file

@ -0,0 +1,8 @@
{
hexdoc: {
"{{ cookiecutter.modid }}": {
title: "{{ cookiecutter.mod_name }} Book",
description: "The {{ cookiecutter.mod_name }} Book, all in one place.",
},
}
}

View file

@ -97,6 +97,8 @@ include = ["doc/src"]
extraPaths = ["doc/src"] extraPaths = ["doc/src"]
exclude = ["doc/{{cookiecutter.directory}}"] exclude = ["doc/{{cookiecutter.directory}}"]
enableExperimentalFeatures = true
# mostly we use strict mode # mostly we use strict mode
# but pyright doesn't allow decreasing error severity in strict mode # but pyright doesn't allow decreasing error severity in strict mode
# so we need to manually specify all of the strict mode overrides so we can do that :/ # so we need to manually specify all of the strict mode overrides so we can do that :/