203 lines
5 KiB
Go
203 lines
5 KiB
Go
/*
|
|
Copyright CAcert Inc.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package services
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
"golang.org/x/text/language"
|
|
|
|
"code.cacert.org/cacert/oidc-demo-app/translations"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
func AddMessages(catalog *MessageCatalog) {
|
|
messages := make(map[string]*i18n.Message)
|
|
messages["AuthenticatedAs"] = &i18n.Message{
|
|
ID: "AuthenticatedAs",
|
|
Other: "The identity provider authenticated your identity as {{ .Name }}" +
|
|
" with the email address {{ .Email }}.",
|
|
}
|
|
messages["GreetingAnonymous"] = &i18n.Message{
|
|
ID: "GreetingAnonymous",
|
|
Other: "Hello",
|
|
}
|
|
messages["GreetingAuthenticated"] = &i18n.Message{
|
|
ID: "GreetingAuthenticated",
|
|
Other: "Hello {{ .Name }}",
|
|
}
|
|
messages["IndexTitle"] = &i18n.Message{
|
|
ID: "IndexTitle",
|
|
Other: "Welcome to the Demo application",
|
|
}
|
|
messages["LoginLabel"] = &i18n.Message{
|
|
ID: "LoginLabel",
|
|
Description: "A label on a login button or link",
|
|
Other: "Login",
|
|
}
|
|
messages["LogoutLabel"] = &i18n.Message{
|
|
ID: "LogoutLabel",
|
|
Description: "A label on a logout button or link",
|
|
Other: "Logout",
|
|
}
|
|
messages["IndexIntroductionText"] = &i18n.Message{
|
|
ID: "IndexIntroductionText",
|
|
Other: "This is a public resource.",
|
|
}
|
|
messages["IndexNavLabel"] = &i18n.Message{
|
|
ID: "IndexNavLabel",
|
|
Description: "Label for the index page in the top navigation",
|
|
Other: "Welcome",
|
|
}
|
|
messages["ProtectedIntroductionText"] = &i18n.Message{
|
|
ID: "ProtectedIntroductionText",
|
|
Other: "This is an authorization protected resource.",
|
|
}
|
|
messages["ProtectedNavLabel"] = &i18n.Message{
|
|
ID: "ProtectedNavLabel",
|
|
Description: "Label for the protected resource page in the top navigation",
|
|
Other: "Protected area",
|
|
}
|
|
|
|
catalog.AddMessages(messages)
|
|
}
|
|
|
|
type MessageCatalog struct {
|
|
messages map[string]*i18n.Message
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func (m *MessageCatalog) AddMessages(messages map[string]*i18n.Message) {
|
|
for key, value := range messages {
|
|
m.messages[key] = value
|
|
}
|
|
}
|
|
|
|
func (m *MessageCatalog) LookupErrorMessage(
|
|
tag string,
|
|
field string,
|
|
value interface{},
|
|
localizer *i18n.Localizer,
|
|
) string {
|
|
fieldTag := fmt.Sprintf("%s-%s", field, tag)
|
|
|
|
message, ok := m.messages[fieldTag]
|
|
if !ok {
|
|
m.logger.Info("no specific error message for field and tag", "field_tag", fieldTag)
|
|
|
|
message, ok = m.messages[tag]
|
|
if !ok {
|
|
m.logger.Info("no specific error message for tag", "tag", tag)
|
|
|
|
message, ok = m.messages["unknown"]
|
|
if !ok {
|
|
m.logger.Warn("no default translation found")
|
|
|
|
return tag
|
|
}
|
|
}
|
|
}
|
|
|
|
translation, err := localizer.Localize(&i18n.LocalizeConfig{
|
|
DefaultMessage: message,
|
|
TemplateData: map[string]interface{}{
|
|
"Value": value,
|
|
},
|
|
})
|
|
if err != nil {
|
|
m.logger.Error("localization failed", "error", err)
|
|
|
|
return tag
|
|
}
|
|
|
|
return translation
|
|
}
|
|
|
|
func (m *MessageCatalog) LookupMessage(
|
|
id string,
|
|
templateData map[string]interface{},
|
|
localizer *i18n.Localizer,
|
|
) string {
|
|
if message, ok := m.messages[id]; ok {
|
|
translation, err := localizer.Localize(&i18n.LocalizeConfig{
|
|
DefaultMessage: message,
|
|
TemplateData: templateData,
|
|
})
|
|
if err != nil {
|
|
return m.handleLocalizeError(id, translation, err)
|
|
}
|
|
|
|
return translation
|
|
}
|
|
|
|
m.logger.Warn("no translation found for id", "id", id)
|
|
|
|
return id
|
|
}
|
|
|
|
func (m *MessageCatalog) handleLocalizeError(id string, translation string, err error) string {
|
|
var messageNotFound *i18n.MessageNotFoundErr
|
|
|
|
if errors.As(err, &messageNotFound) {
|
|
m.logger.Warn("message not found", "error", err, "message", id)
|
|
|
|
if translation != "" {
|
|
return translation
|
|
}
|
|
} else {
|
|
m.logger.Error("translation error", "error", err, "message", id)
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
func InitI18n(logger *slog.Logger, languages []string) (*i18n.Bundle, *MessageCatalog) {
|
|
bundle := i18n.NewBundle(language.English)
|
|
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
|
|
|
for _, lang := range languages {
|
|
bundleName := fmt.Sprintf("active.%s.toml", lang)
|
|
|
|
bundleBytes, err := translations.Bundles.ReadFile(bundleName)
|
|
if err != nil {
|
|
logger.Warn("message bundle not found", "bundle", bundleName)
|
|
|
|
continue
|
|
}
|
|
|
|
bundle.MustParseMessageFileBytes(bundleBytes, bundleName)
|
|
}
|
|
|
|
catalog := initMessageCatalog(logger)
|
|
|
|
return bundle, catalog
|
|
}
|
|
|
|
func initMessageCatalog(logger *slog.Logger) *MessageCatalog {
|
|
messages := make(map[string]*i18n.Message)
|
|
messages["ErrorTitle"] = &i18n.Message{
|
|
ID: "ErrorTitle",
|
|
Other: "An error has occurred",
|
|
}
|
|
|
|
return &MessageCatalog{messages: messages, logger: logger}
|
|
}
|