Localize all of the template text
This commit is contained in:
parent
790521df05
commit
504ec5b48c
21 changed files with 269 additions and 88 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -16,6 +16,7 @@
|
||||||
"editor.rulers": [120],
|
"editor.rulers": [120],
|
||||||
},
|
},
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.js.jinja": "javascript"
|
"*.js.jinja": "javascript",
|
||||||
|
"*.css.jinja": "css",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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 {
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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() }}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
hexdoc: {
|
||||||
|
"{{ cookiecutter.modid }}": {
|
||||||
|
title: "{{ cookiecutter.mod_name }} Book",
|
||||||
|
description: "The {{ cookiecutter.mod_name }} Book, all in one place.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 :/
|
||||||
|
|
Loading…
Reference in a new issue