164 lines
3.9 KiB
Go
164 lines
3.9 KiB
Go
package gonexapp
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
//go:embed i18n/en.json
|
|
var chromeENJSON []byte
|
|
|
|
//go:embed i18n/de.json
|
|
var chromeDEJSON []byte
|
|
|
|
// chromeSets is the Library's own chrome. It is not part of the map the page script sees.
|
|
var chromeSets = map[string]map[string]string{
|
|
"en": mustChrome(chromeENJSON),
|
|
"de": mustChrome(chromeDEJSON),
|
|
}
|
|
|
|
func mustChrome(raw []byte) map[string]string {
|
|
var m map[string]string
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
panic(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
type uiLanguageKey struct{}
|
|
|
|
// UILanguage is the language of the Phrases for this request, "en" or "de".
|
|
// It reads the Requesting user's language from the Provisioning OCS user
|
|
// record and forwards the request's Accept-Language on that read.
|
|
// The locale field is ignored. A more specific code uses the broader
|
|
// language when this Library provides that language. Any other code, a
|
|
// missing Requesting user, or a failed read is English, and the page still renders.
|
|
// A second call with the request returned from a page render reuses that result.
|
|
func (c Credentials) UILanguage(r *http.Request) string {
|
|
if r == nil {
|
|
return "en"
|
|
}
|
|
if lang, ok := r.Context().Value(uiLanguageKey{}).(string); ok {
|
|
return lang
|
|
}
|
|
return c.readUILanguage(r)
|
|
}
|
|
|
|
func (c Credentials) readUILanguage(r *http.Request) string {
|
|
if c.BaseURL == "" {
|
|
return "en"
|
|
}
|
|
userID, err := UserFromRequest(r)
|
|
if err != nil || userID == "" {
|
|
return "en"
|
|
}
|
|
ocs := OCSClient{Cred: c.WithUser(userID)}
|
|
if al := r.Header.Get("Accept-Language"); al != "" {
|
|
ocs.Header = http.Header{"Accept-Language": {al}}
|
|
}
|
|
raw, err := ocs.Call(http.MethodGet, "cloud/users/"+url.PathEscape(userID), nil)
|
|
if err != nil {
|
|
return "en"
|
|
}
|
|
return matchLanguage(userLanguage(raw), chromeHas)
|
|
}
|
|
|
|
func chromeHas(code string) bool {
|
|
_, ok := chromeSets[code]
|
|
return ok
|
|
}
|
|
|
|
// matchLanguage picks a Phrase set for the code Nextcloud returned.
|
|
// The code itself wins when that set exists. Otherwise the part before "_"
|
|
// wins when that set exists. Otherwise English.
|
|
func matchLanguage(code string, has func(string) bool) string {
|
|
code = strings.TrimSpace(code)
|
|
if code != "" && has(code) {
|
|
return code
|
|
}
|
|
if i := strings.IndexByte(code, '_'); i > 0 {
|
|
if base := code[:i]; has(base) {
|
|
return base
|
|
}
|
|
}
|
|
return "en"
|
|
}
|
|
|
|
func userLanguage(raw []byte) string {
|
|
var parsed struct {
|
|
OCS struct {
|
|
Data struct {
|
|
Language string `json:"language"`
|
|
} `json:"data"`
|
|
} `json:"ocs"`
|
|
}
|
|
if err := json.Unmarshal(raw, &parsed); err != nil {
|
|
return ""
|
|
}
|
|
return parsed.OCS.Data.Language
|
|
}
|
|
|
|
func chromePhrase(lang, id string, holes map[string]string) string {
|
|
if _, ok := chromeSets[lang]; !ok {
|
|
lang = "en"
|
|
}
|
|
text := chromeSets[lang][id]
|
|
if text == "" && lang != "en" {
|
|
text = chromeSets["en"][id]
|
|
}
|
|
if text == "" {
|
|
return id
|
|
}
|
|
return fillHoles(text, holes)
|
|
}
|
|
|
|
// fillHoles replaces {name} with the plain-text value.
|
|
// A hole with no value stays visible as {name}.
|
|
// A value is not scanned for further holes.
|
|
func fillHoles(text string, holes map[string]string) string {
|
|
if len(holes) == 0 || !strings.Contains(text, "{") {
|
|
return text
|
|
}
|
|
var b strings.Builder
|
|
b.Grow(len(text))
|
|
for i := 0; i < len(text); {
|
|
if text[i] != '{' {
|
|
b.WriteByte(text[i])
|
|
i++
|
|
continue
|
|
}
|
|
end := strings.IndexByte(text[i+1:], '}')
|
|
if end < 0 {
|
|
b.WriteByte(text[i])
|
|
i++
|
|
continue
|
|
}
|
|
name := text[i+1 : i+1+end]
|
|
value, ok := holes[name]
|
|
if !ok {
|
|
b.WriteByte(text[i])
|
|
i++
|
|
continue
|
|
}
|
|
b.WriteString(value)
|
|
i += end + 2
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// rememberUILanguage returns r carrying lang so a later UILanguage call
|
|
// on this page does not read Nextcloud again.
|
|
func rememberUILanguage(r *http.Request, lang string) *http.Request {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
if _, ok := r.Context().Value(uiLanguageKey{}).(string); ok {
|
|
return r
|
|
}
|
|
return r.WithContext(context.WithValue(r.Context(), uiLanguageKey{}, lang))
|
|
}
|