diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1f0872c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright © 2009, Michael Stapelberg and contributors
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmd/tl8-render/tl8render.go b/cmd/tl8-render/tl8render.go
new file mode 100644
index 0000000..2db9452
--- /dev/null
+++ b/cmd/tl8-render/tl8render.go
@@ -0,0 +1,251 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/google/renameio"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+const preamble = `
+
+
+
+
+
+i3: i3 User’s Guide
+
+
+
+
+
+
+
+
+`
+
+const footer = `
+
+
+`
+
+type translationStatusNode struct {
+ ast.BaseBlock
+
+ translatedVersion string
+}
+
+// Kind implements Node.Kind
+func (n *translationStatusNode) Kind() ast.NodeKind {
+ return kindTranslationStatus
+}
+
+// Dump implements Node.Dump
+func (n *translationStatusNode) Dump(source []byte, level int) {
+ ast.DumpHelper(n, source, level, nil, nil)
+}
+
+var kindTranslationStatus = ast.NewNodeKind("TranslationStatus")
+
+type versionNode struct {
+ ast.BaseBlock
+
+ since string
+}
+
+// Kind implements Node.Kind
+func (n *versionNode) Kind() ast.NodeKind {
+ return kindVersionNode
+}
+
+// Dump implements Node.Dump
+func (n *versionNode) Dump(source []byte, level int) {
+ ast.DumpHelper(n, source, level, nil, nil)
+}
+
+var kindVersionNode = ast.NewNodeKind("VersionNode")
+
+type tl8transformer struct{}
+
+func modifyASTFromHeading(heading ast.Node) {
+ tsn := &translationStatusNode{}
+ vsn := &versionNode{}
+ for _, attr := range heading.Attributes() {
+ b, ok := attr.Value.([]byte)
+ if !ok {
+ continue
+ }
+ val := string(b)
+ if string(attr.Name) == "class" && strings.HasPrefix(val, "since_") {
+ vsn.since = strings.TrimPrefix(val, "since_")
+ }
+ if string(attr.Name) == "translated" {
+ tsn.translatedVersion = val
+ }
+ //log.Printf("heading attr, name=%s, value=%s", attr.Name, val)
+ }
+
+ if vsn.since != "" {
+ // Adding a child will make it part of the heading HTML element
+ // (e.g. )
+ heading.AppendChild(heading, vsn)
+ }
+
+ if tsn.translatedVersion != "" {
+ // Insert the TranslationStatus node after the heading:
+ tsn.SetNextSibling(heading.NextSibling())
+ heading.SetNextSibling(tsn)
+ }
+}
+
+// Transform is called once per document.
+func (t *tl8transformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) {
+ ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+ //log.Printf("ast.Walk(type=%v, kind=%v)", n.Type(), n.Kind())
+ if n.Type() == ast.TypeDocument {
+ return ast.WalkContinue, nil
+ }
+ if n.Kind() == ast.KindHeading && entering {
+ modifyASTFromHeading(n)
+ }
+ return ast.WalkSkipChildren, nil
+ })
+}
+
+func numericVersionToHuman(v string) string {
+ return strings.ReplaceAll(v, "_", ".")
+}
+
+type tl8renderer struct{}
+
+func (r *tl8renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(kindTranslationStatus, r.renderTranslationStatus)
+ reg.Register(kindVersionNode, r.renderVersion)
+}
+
+func (r *tl8renderer) renderVersion(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+ vsn := node.(*versionNode)
+ fmt.Fprintf(w, `since i3 v%s`, numericVersionToHuman(vsn.since))
+ return ast.WalkContinue, nil
+}
+
+func (r *tl8renderer) renderTranslationStatus(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+ tsn := node.(*translationStatusNode)
+
+ // NOTE: Ideally we would link to a list of commits for the relevant file
+ // between the last-translated version and the current version.
+ // Unfortunately, GitHub does not seem to provide such a view.
+
+ translatedVersion := numericVersionToHuman(tsn.translatedVersion)
+ fmt.Fprintf(w, `
+Out-of-date! This section’s translation was last updated for i3 v%s
+(what changed?)
+(contribute)
+`,
+ translatedVersion,
+ "userguide", /* TODO */
+ "userguide" /* TODO */)
+ return ast.WalkContinue, nil
+}
+
+func render1(fn string) error {
+ outfn := strings.TrimSuffix(fn, filepath.Ext(fn)) + ".html"
+ source, err := ioutil.ReadFile(fn)
+ if err != nil {
+ return err
+ }
+
+ md := goldmark.New(
+ // GFM is GitHub Flavored Markdown, which we need for tables, for
+ // example.
+ goldmark.WithExtensions(extension.GFM),
+ goldmark.WithParserOptions(
+ parser.WithAutoHeadingID(),
+ // The Attribute option allows us to id, classes, and arbitrary
+ // options on headings (for translation status).
+ parser.WithAttribute(),
+ parser.WithASTTransformers(util.Prioritized(&tl8transformer{}, 1)),
+ ),
+ goldmark.WithRendererOptions(
+ html.WithHardWraps(),
+ html.WithXHTML(),
+ renderer.WithNodeRenderers(
+ util.Prioritized(&tl8renderer{}, 500)),
+ ),
+ )
+
+ out, err := renameio.TempFile("", outfn)
+ if err != nil {
+ return err
+ }
+
+ out.Write([]byte(preamble))
+
+ if err := md.Convert(source, out); err != nil {
+ return err
+ }
+
+ out.Write([]byte(footer))
+
+ if err := out.CloseAtomicallyReplace(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func tl8render() error {
+ flag.Parse()
+ if flag.NArg() < 1 {
+ return fmt.Errorf("syntax: %s [...]", filepath.Base(os.Args[0]))
+ }
+
+ for _, fn := range flag.Args() {
+ if err := render1(fn); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func main() {
+ if err := tl8render(); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..37fa9e4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,9 @@
+module translation
+
+go 1.15
+
+require (
+ github.com/Kunde21/markdownfmt/v2 v2.0.3
+ github.com/google/renameio v1.0.0
+ github.com/yuin/goldmark v1.3.1
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..e7f2ec0
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,9 @@
+github.com/Kunde21/markdownfmt/v2 v2.0.3 h1:8Xs3GX5hVK0+AiVTXb2xvr8zvrl8Z8DQXOqmA6sXhjk=
+github.com/Kunde21/markdownfmt/v2 v2.0.3/go.mod h1:50JNMOFTYtR8g1f+U8BZlw0M9RL5ZUqjOxxTgITeyrg=
+github.com/google/renameio v1.0.0 h1:xhp2CnJmgQmpJU4RY8chagahUq5mbPPAbiSQstKpVMA=
+github.com/google/renameio v1.0.0/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
+github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/yuin/goldmark v1.1.24/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I=
+github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=