2017-05-07 02:32:32 +02:00
package languages
import (
2017-05-14 21:45:50 +02:00
"errors"
2017-05-09 01:44:41 +02:00
"fmt"
2017-05-13 22:52:10 +02:00
"html/template"
"net/http"
"path"
"path/filepath"
2017-05-17 07:58:40 +02:00
"github.com/NyaaPantsu/nyaa/config"
"github.com/NyaaPantsu/nyaa/model"
2017-05-11 15:12:19 +02:00
"github.com/nicksnyder/go-i18n/i18n"
2017-05-13 00:17:34 +02:00
"github.com/nicksnyder/go-i18n/i18n/language"
2017-05-07 23:05:41 +02:00
)
2017-05-07 02:32:32 +02:00
2017-05-14 21:45:50 +02:00
// this interface is required to prevent a cyclic import between the languages and userService package.
type UserRetriever interface {
RetrieveCurrentUser ( r * http . Request ) ( model . User , error )
}
var (
defaultLanguage string = config . DefaultI18nConfig . DefaultLanguage
userRetriever UserRetriever = nil
)
2017-05-13 22:52:10 +02:00
// Initialize the languages translation
2017-05-14 21:45:50 +02:00
func InitI18n ( conf config . I18nConfig , retriever UserRetriever ) error {
defaultLanguage = conf . DefaultLanguage
userRetriever = retriever
2017-05-13 22:52:10 +02:00
defaultFilepath := path . Join ( conf . TranslationsDirectory , conf . DefaultLanguage + ".all.json" )
err := i18n . LoadTranslationFile ( defaultFilepath )
if err != nil {
panic ( fmt . Sprintf ( "failed to load default translation file '%s': %v" , defaultFilepath , err ) )
}
paths , err := filepath . Glob ( path . Join ( conf . TranslationsDirectory , "*.json" ) )
if err != nil {
return fmt . Errorf ( "failed to get translation files: %v" , err )
}
2017-05-14 21:45:50 +02:00
for _ , file := range paths {
err := i18n . LoadTranslationFile ( file )
2017-05-13 22:52:10 +02:00
if err != nil {
2017-05-14 21:45:50 +02:00
return fmt . Errorf ( "failed to load translation file '%s': %v" , file , err )
2017-05-13 22:52:10 +02:00
}
}
return nil
}
2017-05-14 21:45:50 +02:00
func GetDefaultLanguage ( ) string {
return defaultLanguage
}
2017-05-09 20:24:37 +02:00
// When go-i18n finds a language with >0 translations, it uses it as the Tfunc
// However, if said language has a missing translation, it won't fallback to the "main" language
2017-05-13 00:17:34 +02:00
func TfuncAndLanguageWithFallback ( language string , languages ... string ) ( i18n . TranslateFunc , * language . Language , error ) {
2017-05-09 20:24:37 +02:00
// Use the last language on the args as the fallback one.
fallbackLanguage := language
if languages != nil {
fallbackLanguage = languages [ len ( languages ) - 1 ]
}
2017-05-13 00:17:34 +02:00
T , Tlang , err1 := i18n . TfuncAndLanguage ( language , languages ... )
fallbackT , fallbackTlang , err2 := i18n . TfuncAndLanguage ( fallbackLanguage )
2017-05-09 20:24:37 +02:00
if err1 != nil && err2 != nil {
// fallbackT is still a valid function even with the error, it returns translationID.
2017-05-13 00:17:34 +02:00
return fallbackT , fallbackTlang , err2
2017-05-09 20:24:37 +02:00
}
return func ( translationID string , args ... interface { } ) string {
if translated := T ( translationID , args ... ) ; translated != translationID {
return translated
}
return fallbackT ( translationID , args ... )
2017-05-13 00:17:34 +02:00
} , Tlang , nil
2017-05-09 20:24:37 +02:00
}
2017-05-10 21:45:39 +02:00
func GetAvailableLanguages ( ) ( languages map [ string ] string ) {
languages = make ( map [ string ] string )
var T i18n . TranslateFunc
for _ , languageTag := range i18n . LanguageTags ( ) {
T , _ = i18n . Tfunc ( languageTag )
/ * Translation files should have an ID with the translated language name .
If they don ' t , just use the languageTag * /
if languageName := T ( "language_name" ) ; languageName != "language_name" {
2017-05-11 15:12:19 +02:00
languages [ languageTag ] = languageName
2017-05-10 21:45:39 +02:00
} else {
languages [ languageTag ] = languageTag
}
}
return
}
2017-05-13 00:17:34 +02:00
func setTranslation ( tmpl * template . Template , T i18n . TranslateFunc ) {
2017-05-07 23:05:41 +02:00
tmpl . Funcs ( map [ string ] interface { } {
2017-05-09 01:44:41 +02:00
"T" : func ( str string , args ... interface { } ) template . HTML {
return template . HTML ( fmt . Sprintf ( T ( str ) , args ... ) )
2017-05-07 23:05:41 +02:00
} ,
2017-05-11 13:15:52 +02:00
"Ts" : func ( str string , args ... interface { } ) string {
return fmt . Sprintf ( T ( str ) , args ... )
} ,
2017-05-07 23:05:41 +02:00
} )
}
2017-05-14 21:45:50 +02:00
func GetDefaultTfunc ( ) ( i18n . TranslateFunc , error ) {
return i18n . Tfunc ( defaultLanguage )
}
func GetTfuncAndLanguageFromRequest ( r * http . Request ) ( T i18n . TranslateFunc , Tlang * language . Language ) {
defaultLanguage := GetDefaultLanguage ( )
2017-05-10 21:45:39 +02:00
userLanguage := ""
2017-05-14 21:45:50 +02:00
user , err := getCurrentUser ( r )
2017-05-10 21:45:39 +02:00
if err == nil {
2017-05-11 15:12:19 +02:00
userLanguage = user . Language
2017-05-10 21:45:39 +02:00
}
2017-05-07 23:05:41 +02:00
cookie , err := r . Cookie ( "lang" )
cookieLanguage := ""
if err == nil {
cookieLanguage = cookie . Value
}
2017-05-10 21:45:39 +02:00
2017-05-07 23:05:41 +02:00
// go-i18n supports the format of the Accept-Language header, thankfully.
headerLanguage := r . Header . Get ( "Accept-Language" )
2017-05-13 00:17:34 +02:00
T , Tlang , _ = TfuncAndLanguageWithFallback ( userLanguage , cookieLanguage , headerLanguage , defaultLanguage )
return
}
2017-05-14 21:45:50 +02:00
func SetTranslationFromRequest ( tmpl * template . Template , r * http . Request ) i18n . TranslateFunc {
2017-05-11 15:12:19 +02:00
r . Header . Add ( "Vary" , "Accept-Encoding" )
2017-05-14 21:45:50 +02:00
T , _ := GetTfuncAndLanguageFromRequest ( r )
2017-05-13 00:17:34 +02:00
setTranslation ( tmpl , T )
return T
2017-05-07 23:05:41 +02:00
}
2017-05-14 21:45:50 +02:00
func getCurrentUser ( r * http . Request ) ( model . User , error ) {
if userRetriever == nil {
return model . User { } , errors . New ( "failed to get current user: no user retriever set" )
}
return userRetriever . RetrieveCurrentUser ( r )
}