bskyweb: basic i18n setup

Uses Cookies, then HTTP Accept-Language, then default, to negotiate
language setting.

The cookie name is 'lang', and the value can be any string which would
be valid in Accept-Language. This includes a trivial 2-char ISO code.

Language setting is returned as 2-char ISO code (though could be
more flexible in the future if needed).

HTML language is configured (attr), and a secondary per-language
scripts.html file is included.

As a hack Makefile generates the scripts_*.html files; typescript build
system will need to populate those appropriately as part of 'make
build-web' to finish integration.
This commit is contained in:
bryan newbold
2023-11-22 14:33:25 -08:00
parent 08333002cc
commit 034e6bdf66
5 changed files with 53 additions and 3 deletions
+1
View File
@@ -9,6 +9,7 @@ static/js/*.js
static/js/*.map
static/js/*.js.LICENSE.txt
templates/scripts.html
templates/scripts_*.html
# Don't ignore this file
!.gitignore
+1 -1
View File
@@ -42,4 +42,4 @@ check: ## Compile everything, checking syntax (does not output binaries)
.PHONY: run-dev-bskyweb
run-dev-bskyweb: .env ## Runs 'bskyweb' for local dev
GOLOG_LOG_LEVEL=info go run ./cmd/bskyweb serve --debug
GOLOG_LOG_LEVEL=info go run ./cmd/bskyweb serve
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"github.com/labstack/echo/v4"
"golang.org/x/text/language"
)
// this constant determines the supported languages. the first in the array is used as a fallback.
//
// NOTE: intentionally using 2-char codes here instead of the named constants
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
var langMatcher = language.NewMatcher([]language.Tag{
language.MustParse("en"), // English: default/fallback
language.MustParse("ja"), // Japanese
language.MustParse("pt"), // Portuguese
language.MustParse("de"), // German
language.MustParse("hi"), // Hindi
})
// cookie value should have same syntax as an Accept-Language HTTP header
var langCookieName = "lang"
// returns an ISO 639 2-char language code to use for this request.
//
// tries a cookie first, then Accept-Language HTTP header, then a default
func determineLangCode(c echo.Context) string {
cookieVal := ""
cookie, err := c.Cookie(langCookieName)
if err != nil {
c.Logger().Warn(err)
} else {
cookieVal = cookie.Value
}
hdr := c.Request().Header.Get("Accept-Language")
langTag, _ := language.MatchStrings(langMatcher, cookieVal, hdr)
base, _ := langTag.Base()
return base.String()
}
+4
View File
@@ -276,16 +276,19 @@ func (srv *Server) errorHandler(err error, c echo.Context) {
// handler for endpoint that have no specific server-side handling
func (srv *Server) WebGeneric(c echo.Context) error {
data := pongo2.Context{}
data["lang"] = determineLangCode(c)
return c.Render(http.StatusOK, "base.html", data)
}
func (srv *Server) WebHome(c echo.Context) error {
data := pongo2.Context{}
data["lang"] = determineLangCode(c)
return c.Render(http.StatusOK, "home.html", data)
}
func (srv *Server) WebPost(c echo.Context) error {
data := pongo2.Context{}
data["lang"] = determineLangCode(c)
handle := c.Param("handle")
rkey := c.Param("rkey")
// sanity check argument
@@ -321,6 +324,7 @@ func (srv *Server) WebPost(c echo.Context) error {
func (srv *Server) WebProfile(c echo.Context) error {
data := pongo2.Context{}
data["lang"] = determineLangCode(c)
handle := c.Param("handle")
// sanity check argument
if len(handle) > 4 && len(handle) < 128 {
+7 -2
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<!DOCTYPE html>
<html lang="{{ lang }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover">
@@ -172,6 +172,11 @@
}
</style>
{% include "scripts.html" %}
{% if lang == "en" %}{% include "scripts_en.html" %}{% endif %}
{% if lang == "ja" %}{% include "scripts_ja.html" %}{% endif %}
{% if lang == "pt" %}{% include "scripts_pt.html" %}{% endif %}
{% if lang == "de" %}{% include "scripts_de.html" %}{% endif %}
{% if lang == "hi" %}{% include "scripts_hi.html" %}{% endif %}
<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">