commit d792585058e20eaf11d6aeec0ef8a7b582882437
parent da670002453298780caaed765fb0208a6378ab5f
Author: cblgh <cblgh@cblgh.org>
Date: Mon, 19 Sep 2022 13:03:21 +0200
i18n work, and enable communities to more easily customize about text
Diffstat:
7 files changed, 108 insertions(+), 23 deletions(-)
diff --git a/about.md b/about.md
@@ -0,0 +1,51 @@
+# About
+
+This forum is for and by the [Merveilles](https://wiki.xxiivv.com/site/merveilles.html)
+community.
+
+The [forum software](https://github.com/cblgh/cerca) itself was created from scratch by
+[cblgh](https://cblgh.org) at the start of 2022, after a long time of pining for a new wave of
+forums hangs.
+
+If you are from Merveilles: [register](/register) an account. If you're a passerby, feel free to read the [public threads](/).
+
+## Code of conduct
+
+As with all Merveilles spaces, this forum abides by the compact set out in the [Merveilles Code
+of Conduct](https://github.com/merveilles/Resources/blob/master/CONDUCT.md).
+
+## Forum syntax
+
+Posts in the forum are made using [Markdown syntax](https://en.wikipedia.org/wiki/Markdown#Examples).
+
+<b>\*\*Bold text\*\*</b> and <i>\*italics\*</i>
+
+<ul>
+ <li>* lists</li>
+ <li>* like </li>
+ <li>* this </li>
+</ul>
+
+<blockquote>> Blockquote</blockquote>
+
+<code>\`typewriter text\`</code>
+
+<!-- leave the <pre><code> blocks from reformatting! they render all their spacing :)-->
+<pre><code>```
+blocks of
+code like
+this
+```
+</code></pre>
+
+Create links like <code>\[this\]\(url\)</code>, and embed images like: <code>!\[description\]\(url\)</code>. Note how the image
+syntax's exclamation mark precedes the regular link syntax.
+
+Each post in the thread can be referenced like <code>\[this post\]\(#12\)</code>, where 12 is the post number which can be
+found at each post timestamp.
+
+<pre><code>this is one paragraph.
+this belongs to the same paragraph.
+
+this is a new paragraph
+</code></pre>
diff --git a/html/about-template.html b/html/about-template.html
@@ -0,0 +1,7 @@
+{{ template "head" . }}
+<main>
+ <article>
+ {{ .Data }}
+ </article>
+</main>
+{{ template "footer" . }}
diff --git a/html/head.html b/html/head.html
@@ -206,13 +206,13 @@
</li>
{{ end }}
{{ if .QuickNav }}
- <li><a href="#bottom">bottom</a></li>
+ <li><a href="#bottom">{{ "Bottom" | translate }}</a></li>
{{end}}
- <li><a href="/about">about</a></li>
+ <li><a href="/about">{{ "About" | translate }}</a></li>
{{ if .LoggedIn }}
- <li><a href="/logout">logout</a></li>
+ <li><a href="/logout">{{"Logout" | translate }}</a></li>
{{ else }}
- <li><a href="/login">login</a></li>
+ <li><a href="/login">{{ "Login" | translate }}</a></li>
{{ end }}
</ul>
</nav>
diff --git a/html/login-component.html b/html/login-component.html
@@ -2,15 +2,15 @@
<form method="post" action="/login">
<div style="display: grid;">
<div>
- <label for="username">Username:</label>
+ <label for="username">{{ "Username" | translate | capitalize }}:</label>
<input type="text" name="username" id="username">
</div>
<div>
- <label for="password">Password:</label>
+ <label for="password">{{ "Password" | translate | capitalize }}:</label>
<input type="password" name="password" id="password" style="margin-bottom:0;" aria-describedby="password-help">
- <div><small id="password-help">Must be at least 9 characters long.</small></div>
+ <div><small id="password-help">{{ "PasswordMin" | translate }}</small></div>
</div>
- <input type="submit" value="Enter" style="margin-top:1rem;">
+ <input type="submit" value='{{ "Enter" | translate | capitalize }}' style="margin-top:1rem;">
</div>
</form>
{{ end }}
diff --git a/html/login.html b/html/login.html
@@ -1,10 +1,10 @@
{{ template "head" . }}
<main>
- <h1>Login</h1>
- <p>This forum is for the <a href="https://wiki.xxiivv.com/site/merveilles.html">Merveilles</a> community. Don't have an account yet? <a href="/register">Register</a> one. </p>
+ <h1>{{ "Login" | translate | capitalize }}</h1>
+ <p>{{ "LoginDescription" | translateWithData | capitalize | tohtml }} {{ "LoginNoAccount" | translate | tohtml }}</p>
<div style="max-width: 20rem">
{{ template "login-component" . }}
- <p><a href="/reset">Forgot your password?</a></p>
+ <p><a href="/reset">{{ "PasswordForgot" | translate }}</a></p>
</div>
{{ if .Data.FailedAttempt }}
<p><b>Failed login attempt:</b> incorrect password, wrong username, or a non-existent user.</p>
diff --git a/i18n/i18n.go b/i18n/i18n.go
@@ -2,10 +2,9 @@ package i18n
import (
"cerca/util"
- "text/template"
+ "html/template"
"strings"
"log"
- "fmt"
)
var English = map[string]string{
@@ -31,13 +30,13 @@ var EspanolMexicano = map[string]string{
"Sort": "sort",
"SortPostsRecent": "recent posts",
"SortThreadsRecent": "most recent threads",
- "LoginDescription": "Este foro es principalmente para las personas de la comunidad <a href='{{.CommunityLink}}>{{.CommunityName}}</a>.",
+ "LoginDescription": "Este foro es principalmente para las personas de la comunidad <a href='{{ .CommunityLink }}'>{{ .CommunityName }}</a>.",
"LoginNoAccount": "¿No tienes una cuenta? <a href='/register'>Registra</a> una. ",
"Username": "usuarie",
"Password": "contraseña",
"PasswordMin": "Debe tener por lo menos 9 caracteres.",
"PasswordForgot": "Olvidaste tu contraseña?",
- "Enter": "enter",
+ "Enter": "entrar",
}
var translations = map[string]map[string]string{
@@ -77,8 +76,7 @@ func Init(lang string) Translator {
return Translator{lang}
}
-func main() {
- tr := Init("EnglishSwedish")
- fmt.Println(tr.Translate("LoginNoAccount"))
- fmt.Println(tr.TranslateWithData("LoginDescription", Community{"Merveilles", "https://merveill.es"}))
-}
+// usage:
+// tr := Init("EnglishSwedish")
+// fmt.Println(tr.Translate("LoginNoAccount"))
+// fmt.Println(tr.TranslateWithData("LoginDescription", Community{"Merveilles", "https://merveill.es"}))
diff --git a/server/server.go b/server/server.go
@@ -15,6 +15,7 @@ import (
"strings"
"time"
+ "cerca/i18n"
"cerca/crypto"
"cerca/database"
cercaHTML "cerca/html"
@@ -108,6 +109,9 @@ func (h RequestHandler) IsLoggedIn(req *http.Request) (bool, int) {
}
var (
+ translator = i18n.Init("EspañolMexicano")
+ community = i18n.Community{"Merveilles", "https://wiki.xxiivv.com/site/merveilles.html"}
+
templateFuncs = template.FuncMap{
"formatDateTime": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
@@ -127,6 +131,23 @@ var (
}
return t.Format("2006-01-02")
},
+ "translate": func(key string) string {
+ return translator.Translate(key)
+ },
+ "translateWithData": func(key string) string {
+ return translator.TranslateWithData(key, community)
+ },
+ "capitalize": func (s string) string {
+ return strings.ToUpper(string(s[0])) + s[1:]
+ },
+ "tohtml": func (s string) template.HTML {
+ // use of this function is risky cause it interprets the passed in string and renders it as unescaped html.
+ // can allow for attacks!
+ //
+ // advice: only use on strings that come statically from within cerca code, never on titles that may contain user-submitted data
+ // :)
+ return (template.HTML)(s)
+ },
}
templates = template.Must(generateTemplates())
@@ -135,6 +156,7 @@ var (
func generateTemplates() (*template.Template, error) {
views := []string{
"about",
+ "about-template",
"footer",
"generic-message",
"head",
@@ -264,7 +286,7 @@ func (h RequestHandler) LoginRoute(res http.ResponseWriter, req *http.Request) {
loggedIn, _ := h.IsLoggedIn(req)
switch req.Method {
case "GET":
- h.renderView(res, "login", TemplateData{Data: LoginData{}, LoggedIn: loggedIn, Title: ""})
+ h.renderView(res, "login", TemplateData{Data: LoginData{}, LoggedIn: loggedIn, Title: translator.Translate("Login")})
case "POST":
username := req.PostFormValue("username")
password := req.PostFormValue("password")
@@ -276,7 +298,7 @@ func (h RequestHandler) LoginRoute(res http.ResponseWriter, req *http.Request) {
}
if err != nil {
fmt.Println(err)
- h.renderView(res, "login", TemplateData{Data: LoginData{FailedAttempt: true}, LoggedIn: loggedIn, Title: ""})
+ h.renderView(res, "login", TemplateData{Data: LoginData{FailedAttempt: true}, LoggedIn: loggedIn, Title: translator.Translate("Login")})
return
}
// save user id in cookie
@@ -564,7 +586,14 @@ func (h RequestHandler) GenericRoute(res http.ResponseWriter, req *http.Request)
func (h RequestHandler) AboutRoute(res http.ResponseWriter, req *http.Request) {
loggedIn, _ := h.IsLoggedIn(req)
- h.renderView(res, "about", TemplateData{LoggedIn: loggedIn})
+ // TODO (2022-09-19):
+ // * make sure file exists
+ // * create function to output a prefilled version, using the Community name and CommunityLink
+ // * embed the prefilled version in the code using golang's goembed
+ b, err := os.ReadFile("data/about.md")
+ util.Check(err, "about route: open about.md")
+ input := util.Markup(template.HTML(b))
+ h.renderView(res, "about-template", TemplateData{Data: input, LoggedIn: loggedIn})
}
func (h RequestHandler) RobotsRoute(res http.ResponseWriter, req *http.Request) {