♻️ Replace i18n services with nexus one

This commit is contained in:
LittleSheep 2025-02-02 14:28:03 +08:00
parent ec0048042a
commit eaa8fb5225
10 changed files with 38 additions and 119 deletions

14
.idea/workspace.xml generated
View File

@ -4,9 +4,17 @@
<option name="autoReloadType" value="ALL" /> <option name="autoReloadType" value="ALL" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":sparkles: Register with preferred language"> <list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":necktie: Limit max auth steps to 2 for normal users">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.sum" beforeDir="false" afterPath="$PROJECT_DIR$/go.sum" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/gap/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/gap/server.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/services/factors.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/factors.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/services/i18n.go" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/services/reports.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/reports.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/services/ticket.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/ticket.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pkg/internal/services/ticket.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/ticket.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/internal/services/tokens.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/internal/services/tokens.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pkg/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/main.go" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -159,7 +167,6 @@
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" /> <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<MESSAGE value=":boom: Passing relationship api arguments in body instead of querystring" />
<MESSAGE value=":sparkles: Better check in experience random algorithm" /> <MESSAGE value=":sparkles: Better check in experience random algorithm" />
<MESSAGE value=":sparkles: Better relationships stauts query" /> <MESSAGE value=":sparkles: Better relationships stauts query" />
<MESSAGE value=":truck: Move make friendship api" /> <MESSAGE value=":truck: Move make friendship api" />
@ -184,7 +191,8 @@
<MESSAGE value=":bug: Bug fixes on localization" /> <MESSAGE value=":bug: Bug fixes on localization" />
<MESSAGE value=":bug: Fix email html rendering" /> <MESSAGE value=":bug: Fix email html rendering" />
<MESSAGE value=":sparkles: Register with preferred language" /> <MESSAGE value=":sparkles: Register with preferred language" />
<option name="LAST_COMMIT_MESSAGE" value=":sparkles: Register with preferred language" /> <MESSAGE value=":necktie: Limit max auth steps to 2 for normal users" />
<option name="LAST_COMMIT_MESSAGE" value=":necktie: Limit max auth steps to 2 for normal users" />
<option name="GROUP_MULTIFILE_MERGE_BY_DIRECTORY" value="true" /> <option name="GROUP_MULTIFILE_MERGE_BY_DIRECTORY" value="true" />
</component> </component>
<component name="VgoProject"> <component name="VgoProject">

2
go.mod
View File

@ -3,7 +3,7 @@ module git.solsynth.dev/hypernet/passport
go 1.23.2 go 1.23.2
require ( require (
git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b git.solsynth.dev/hypernet/nexus v0.0.0-20250202054714-6de240179f9c
git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465 git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465
git.solsynth.dev/hypernet/wallet v0.0.0-20250129150034-87b94cdb5488 git.solsynth.dev/hypernet/wallet v0.0.0-20250129150034-87b94cdb5488
github.com/dgraph-io/ristretto v0.2.0 github.com/dgraph-io/ristretto v0.2.0

2
go.sum
View File

@ -35,6 +35,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b h1:8yB9kMwEMY/nIbmDDxrhH5sTypgmK5PIIiIfP5QXx4s= git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b h1:8yB9kMwEMY/nIbmDDxrhH5sTypgmK5PIIiIfP5QXx4s=
git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b/go.mod h1:PhLCv2lsNoscPVJbkWnxwQnJ141lc4RIEkVffrHwl4s= git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b/go.mod h1:PhLCv2lsNoscPVJbkWnxwQnJ141lc4RIEkVffrHwl4s=
git.solsynth.dev/hypernet/nexus v0.0.0-20250202054714-6de240179f9c h1:z0//UGRwyZq1TIvn5/fGK5GCXr837KLFD3K0AkaKDyY=
git.solsynth.dev/hypernet/nexus v0.0.0-20250202054714-6de240179f9c/go.mod h1:v+rpf1ZDRi8moaThTAkj5DMQU+rw96YTHcN8/7n/p2Y=
git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465 h1:KFtv9lF0JMUGsq1uHwQvop8PTyqdiLuUQuRrd5WmzPk= git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465 h1:KFtv9lF0JMUGsq1uHwQvop8PTyqdiLuUQuRrd5WmzPk=
git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465/go.mod h1:XHTqFU/vBe4JiuAjl87GUcL8+w/IizSNoqH6n3WkQFc= git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465/go.mod h1:XHTqFU/vBe4JiuAjl87GUcL8+w/IizSNoqH6n3WkQFc=
git.solsynth.dev/hypernet/wallet v0.0.0-20250129150034-87b94cdb5488 h1:/9Ol+PfDQFAYtHo0kk6sxqiEsZ6epb6yUEsZJxy14Mk= git.solsynth.dev/hypernet/wallet v0.0.0-20250129150034-87b94cdb5488 h1:/9Ol+PfDQFAYtHo0kk6sxqiEsZ6epb6yUEsZJxy14Mk=

View File

@ -3,6 +3,7 @@ package gap
import ( import (
"errors" "errors"
"fmt" "fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/localize"
"strings" "strings"
"time" "time"
@ -81,3 +82,7 @@ func InitializeToNexus() error {
return err return err
} }
func LoadLocalization() error {
return localize.LoadLocalization(viper.GetString("locales_path"), viper.GetString("templates_path"))
}

View File

@ -2,6 +2,7 @@ package services
import ( import (
"fmt" "fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/localize"
"strings" "strings"
"time" "time"
@ -76,8 +77,8 @@ func GetFactorCode(factor models.AuthFactor, ip string) (bool, error) {
err = PushNotification(models.Notification{ err = PushNotification(models.Notification{
Topic: "passport.security.otp", Topic: "passport.security.otp",
Title: GetLocalizedString("subjectLoginOneTimePassword", user.Language), Title: localize.L.GetLocalizedString("subjectLoginOneTimePassword", user.Language),
Body: fmt.Sprintf(GetLocalizedString("shortBodyLoginOneTimePassword", user.Language), secret), Body: fmt.Sprintf(localize.L.GetLocalizedString("shortBodyLoginOneTimePassword", user.Language), secret),
Account: user, Account: user,
AccountID: user.ID, AccountID: user.ID,
Metadata: map[string]any{"secret": secret}, Metadata: map[string]any{"secret": secret},
@ -105,9 +106,9 @@ func GetFactorCode(factor models.AuthFactor, ip string) (bool, error) {
log.Info().Uint("factor", factor.ID).Str("secret", secret).Msg("Published one-time-password to JetStream...") log.Info().Uint("factor", factor.ID).Str("secret", secret).Msg("Published one-time-password to JetStream...")
} }
subject := fmt.Sprintf("[%s] %s", viper.GetString("name"), GetLocalizedString("subjectLoginOneTimePassword", user.Language)) subject := fmt.Sprintf("[%s] %s", viper.GetString("name"), localize.L.GetLocalizedString("subjectLoginOneTimePassword", user.Language))
content := RenderLocalizedTemplateHTML("email-otp.tmpl", user.Language, map[string]any{ content := localize.L.RenderLocalizedTemplateHTML("email-otp.tmpl", user.Language, map[string]any{
"Code": secret, "Code": secret,
"User": user, "User": user,
"IP": ip, "IP": ip,

View File

@ -1,100 +0,0 @@
package services
import (
"errors"
"fmt"
"github.com/goccy/go-json"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"golang.org/x/text/language"
htmpl "html/template"
"os"
"path/filepath"
"strings"
)
const FallbackLanguage = "en-US"
var LocaleBundle *i18n.Bundle
func LoadLocalization() error {
LocaleBundle = i18n.NewBundle(language.AmericanEnglish)
LocaleBundle.RegisterUnmarshalFunc("json", json.Unmarshal)
var count int
basePath := viper.GetString("locales_dir")
if entries, err := os.ReadDir(basePath); err != nil {
return fmt.Errorf("unable to read locales directory: %v", err)
} else {
for _, entry := range entries {
if entry.IsDir() {
continue
}
if _, err := LocaleBundle.LoadMessageFile(filepath.Join(basePath, entry.Name())); err != nil {
return fmt.Errorf("unable to load localization file %s: %v", entry.Name(), err)
} else {
count++
}
}
}
log.Info().Int("locales", count).Msg("Loaded localization files...")
return nil
}
func GetLocalizer(lang string) *i18n.Localizer {
return i18n.NewLocalizer(LocaleBundle, lang)
}
func GetLocalizedString(name string, lang string) string {
localizer := GetLocalizer(lang)
msg, err := localizer.LocalizeMessage(&i18n.Message{
ID: name,
})
if err != nil {
log.Warn().Err(err).Str("lang", lang).Str("name", name).Msg("Failed to localize string...")
return name
}
return msg
}
func GetLocalizedTemplatePath(name string, lang string) string {
basePath := viper.GetString("templates_dir")
filePath := filepath.Join(basePath, lang, name)
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
// Fallback to English
filePath = filepath.Join(basePath, FallbackLanguage, name)
return filePath
}
return filePath
}
func GetLocalizedTemplateHTML(name string, lang string) *htmpl.Template {
path := GetLocalizedTemplatePath(name, lang)
tmpl, err := htmpl.ParseFiles(path)
if err != nil {
log.Warn().Err(err).Str("lang", lang).Str("name", name).Msg("Failed to load localized template...")
return nil
}
return tmpl
}
func RenderLocalizedTemplateHTML(name string, lang string, data any) string {
tmpl := GetLocalizedTemplateHTML(name, lang)
if tmpl == nil {
return ""
}
buf := new(strings.Builder)
err := tmpl.Execute(buf, data)
if err != nil {
log.Warn().Err(err).Str("lang", lang).Str("name", name).Msg("Failed to render localized template...")
return ""
}
return buf.String()
}

View File

@ -2,6 +2,7 @@ package services
import ( import (
"fmt" "fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/localize"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models" "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/passport/pkg/internal/database" "git.solsynth.dev/hypernet/passport/pkg/internal/database"
) )
@ -42,8 +43,8 @@ func UpdateAbuseReportStatus(id uint, status, message string) error {
_ = NewNotification(models.Notification{ _ = NewNotification(models.Notification{
Topic: "reports.feedback", Topic: "reports.feedback",
Title: GetLocalizedString("subjectAbuseReportUpdated", account.Language), Title: localize.L.GetLocalizedString("subjectAbuseReportUpdated", account.Language),
Body: fmt.Sprintf(GetLocalizedString("shortBodyAbuseReportUpdated", account.Language), id, status, message), Body: fmt.Sprintf(localize.L.GetLocalizedString("shortBodyAbuseReportUpdated", account.Language), id, status, message),
Account: account, Account: account,
AccountID: account.ID, AccountID: account.ID,
}) })

View File

@ -2,6 +2,7 @@ package services
import ( import (
"fmt" "fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/localize"
"time" "time"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models" "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
@ -137,8 +138,8 @@ func ActiveTicket(ticket models.AuthTicket) (models.AuthTicket, error) {
_ = NewNotification(models.Notification{ _ = NewNotification(models.Notification{
Topic: "passport.security.alert", Topic: "passport.security.alert",
Title: GetLocalizedString("subjectLoginAlert", account.Language), Title: localize.L.GetLocalizedString("subjectLoginAlert", account.Language),
Body: fmt.Sprintf(GetLocalizedString("shortBodyLoginAlert", account.Language), ticket.IpAddress), Body: fmt.Sprintf(localize.L.GetLocalizedString("shortBodyLoginAlert", account.Language), ticket.IpAddress),
Metadata: datatypes.JSONMap{ Metadata: datatypes.JSONMap{
"ip_address": ticket.IpAddress, "ip_address": ticket.IpAddress,
"created_at": ticket.CreatedAt, "created_at": ticket.CreatedAt,

View File

@ -2,6 +2,7 @@ package services
import ( import (
"fmt" "fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/localize"
"strings" "strings"
"time" "time"
@ -62,22 +63,22 @@ func NotifyMagicToken(token models.MagicToken) error {
switch token.Type { switch token.Type {
case models.ConfirmMagicToken: case models.ConfirmMagicToken:
link := fmt.Sprintf("%s/flow/accounts/confirm?code=%s", viper.GetString("frontend_app"), token.Code) link := fmt.Sprintf("%s/flow/accounts/confirm?code=%s", viper.GetString("frontend_app"), token.Code)
subject = fmt.Sprintf("[%s] %s", viper.GetString("name"), GetLocalizedString("subjectConfirmRegistration", user.Language)) subject = fmt.Sprintf("[%s] %s", viper.GetString("name"), localize.L.GetLocalizedString("subjectConfirmRegistration", user.Language))
content = RenderLocalizedTemplateHTML("register-confirm.tmpl", user.Language, map[string]any{ content = localize.L.RenderLocalizedTemplateHTML("register-confirm.tmpl", user.Language, map[string]any{
"User": user, "User": user,
"Link": link, "Link": link,
}) })
case models.ResetPasswordMagicToken: case models.ResetPasswordMagicToken:
link := fmt.Sprintf("%s/flow/accounts/password-reset?code=%s", viper.GetString("frontend_app"), token.Code) link := fmt.Sprintf("%s/flow/accounts/password-reset?code=%s", viper.GetString("frontend_app"), token.Code)
subject = fmt.Sprintf("[%s] %s", viper.GetString("name"), GetLocalizedString("subjectResetPassword", user.Language)) subject = fmt.Sprintf("[%s] %s", viper.GetString("name"), localize.L.GetLocalizedString("subjectResetPassword", user.Language))
content = RenderLocalizedTemplateHTML("reset-password.tmpl", user.Language, map[string]any{ content = localize.L.RenderLocalizedTemplateHTML("reset-password.tmpl", user.Language, map[string]any{
"User": user, "User": user,
"Link": link, "Link": link,
}) })
case models.DeleteAccountMagicToken: case models.DeleteAccountMagicToken:
link := fmt.Sprintf("%s/flow/accounts/deletion?code=%s", viper.GetString("frontend_app"), token.Code) link := fmt.Sprintf("%s/flow/accounts/deletion?code=%s", viper.GetString("frontend_app"), token.Code)
subject = fmt.Sprintf("[%s] %s", viper.GetString("name"), GetLocalizedString("subjectDeleteAccount", user.Language)) subject = fmt.Sprintf("[%s] %s", viper.GetString("name"), localize.L.GetLocalizedString("subjectDeleteAccount", user.Language))
content = RenderLocalizedTemplateHTML("confirm-deletion.tmpl", user.Language, map[string]any{ content = localize.L.RenderLocalizedTemplateHTML("confirm-deletion.tmpl", user.Language, map[string]any{
"User": user, "User": user,
"Link": link, "Link": link,
}) })

View File

@ -72,7 +72,7 @@ func main() {
} }
// Load localization // Load localization
if err := services.LoadLocalization(); err != nil { if err := gap.LoadLocalization(); err != nil {
log.Fatal().Err(err).Msg("An error occurred when loading localization.") log.Fatal().Err(err).Msg("An error occurred when loading localization.")
} }