cerca

lean forum software (pmc local branch)
Log | Files | Refs | README | LICENSE

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:
Aabout.md | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Ahtml/about-template.html | 7+++++++
Mhtml/head.html | 8++++----
Mhtml/login-component.html | 8++++----
Mhtml/login.html | 6+++---
Mi18n/i18n.go | 16+++++++---------
Mserver/server.go | 35++++++++++++++++++++++++++++++++---
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>&gt; 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) {