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)) }