86 lines
2.3 KiB
Go
86 lines
2.3 KiB
Go
package translate
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
func (s *Service) Middleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := r.URL.Path
|
|
segments := []string{}
|
|
for segment := range strings.SplitSeq(path, "/") {
|
|
if segment != "" {
|
|
segments = append(segments, segment)
|
|
}
|
|
}
|
|
|
|
// 1. Check if the first segment is a supported language.
|
|
var currentLang string
|
|
if len(segments) >= 1 {
|
|
potentialLang := segments[0]
|
|
if _, ok := s.translations[potentialLang]; ok {
|
|
currentLang = potentialLang
|
|
}
|
|
}
|
|
|
|
if currentLang != "" {
|
|
// CASE A: Language is present in the URL.
|
|
// Remove the language from the path so the router sees the clean path.
|
|
newPath := "/" + strings.Join(segments[1:], "/")
|
|
if newPath == "" {
|
|
newPath = "/"
|
|
}
|
|
|
|
// Update the request URL path.
|
|
r.URL.Path = newPath
|
|
|
|
// Inject translator into context.
|
|
ctx := context.WithValue(r.Context(), serviceKey, s)
|
|
ctx = context.WithValue(ctx, langKey, currentLang)
|
|
|
|
// Pass control to the next handler with the modified request.
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
return
|
|
}
|
|
|
|
// CASE B: Language is NOT in the URL.
|
|
// Detect best language from Accept-Language header.
|
|
detectedLang := s.detectLanguage(r.Header.Get("Accept-Language"))
|
|
|
|
// Redirect to the URL with the language prefix.
|
|
targetURL := "/" + detectedLang + path
|
|
if r.URL.RawQuery != "" {
|
|
targetURL += "?" + r.URL.RawQuery
|
|
}
|
|
|
|
http.Redirect(w, r, targetURL, http.StatusFound)
|
|
})
|
|
}
|
|
|
|
// detectLanguage parses the Accept-Language header and returns the best matching supported language.
|
|
func (s *Service) detectLanguage(header string) string {
|
|
if header == "" {
|
|
return s.defaultLanguage
|
|
}
|
|
|
|
// Simple parser for Accept-Language: en-US,en;q=0.9,de;q=0.8
|
|
// We only care about the first match for simplicity here, or we could rank them.
|
|
parts := strings.Split(header, ",")
|
|
for _, part := range parts {
|
|
tag := strings.Split(strings.TrimSpace(part), ";")[0]
|
|
// Check for exact match (e.g., "en-US")
|
|
if _, ok := s.translations[tag]; ok {
|
|
return tag
|
|
}
|
|
// Check for base match (e.g., "en" from "en-US")
|
|
base := strings.Split(tag, "-")[0]
|
|
if _, ok := s.translations[base]; ok {
|
|
return base
|
|
}
|
|
}
|
|
|
|
return s.defaultLanguage
|
|
}
|