Documentation
¶
Overview ¶
Package translation provides a type-safe, declarative system for translating model content stored per-record (distinct from i18n, which localizes static catalog/error strings).
Translatable fields are marked with struct tags, similar to json/db/validate. Each model implements the Translatable interface to expose its model name and instance key. The Translator reads and writes translations through a Store; the Postgres implementation lives in the postgres subpackage, and an in-memory MockStore is provided for tests.
Declaring a model ¶
type Product struct {
ID string `json:"id" translate:"primary"`
Title string `json:"title" translate:"title"`
Description string `json:"description" translate:"description"`
}
func (s *Product) GetTranslationKey() (modelName, keyID string) {
return "products", s.ID
}
Only the primary key field is required; every other tagged field must be a string. Fields without a translate tag are ignored.
Setup ¶
store := postgres.NewStore(log, db)
tr, err := translation.New(translation.Config{
Store: store,
DefaultLanguage: translation.LanguageRu,
SupportedLangs: []translation.Language{translation.LanguageRu, translation.LanguageKk},
})
Automatic translation in REST ¶
The translationrest subpackage installs one middleware that resolves the request language and translates the response in place, so handlers only return their DTO:
r := router.New(errorMapper, translationrest.Middleware(log, tr))
A response is translated when its rest.ResponseEncoder is a Translatable (single model) or a TranslatableList (collection — translated in one batch query). Translating to the default language is a no-op.
Manual use ¶
// Single model: err := tr.Translate(ctx, product, translation.LanguageKk) // Batch, type-safe: err := translation.TranslateSlice(ctx, tr, products, translation.LanguageKk) // CRUD on the stored translations: err := tr.Save(ctx, translation.LanguageKk, product) err := tr.Get(ctx, translation.LanguageKk, product) err := tr.Delete(ctx, translation.LanguageKk, product)
Testing ¶
tr, _ := translation.New(translation.Config{Store: translation.NewMockStore(), ...})
Index ¶
- Variables
- func SetLanguageInContext(ctx context.Context, lang Language) context.Context
- func TranslateSlice[T Translatable](ctx context.Context, t *Translator, models []T, lang Language) error
- type Config
- type Data
- type HeaderSource
- type Language
- type MockStore
- func (m *MockStore) CheckTranslationsExist(ctx context.Context, modelName, keyID string, columns []string, ...) error
- func (m *MockStore) DeleteTranslations(ctx context.Context, modelName, keyID string, lang Language) error
- func (m *MockStore) GetTranslations(ctx context.Context, modelName, keyID string, lang Language) (map[string]string, error)
- func (m *MockStore) GetTranslationsBatch(ctx context.Context, modelName string, keyIDs []string, lang Language) (map[string]map[string]string, error)
- func (m *MockStore) SaveTranslation(ctx context.Context, data Data) error
- type Store
- type Translatable
- type TranslatableList
- type Translator
- func (t *Translator) CheckTranslationsExist(ctx context.Context, model Translatable) error
- func (t *Translator) DefaultLanguage() Language
- func (t *Translator) Delete(ctx context.Context, lang Language, model Translatable) error
- func (t *Translator) Get(ctx context.Context, lang Language, model Translatable) error
- func (t *Translator) HTTPMiddleware(next http.Handler) http.Handler
- func (t *Translator) IsDefaultLanguage(lang Language) bool
- func (t *Translator) LanguageFrom(r *http.Request, sources ...HeaderSource) Language
- func (t *Translator) LanguageFromRequest(r *http.Request) Language
- func (t *Translator) LanguageMiddleware(sources ...HeaderSource) func(http.Handler) http.Handler
- func (t *Translator) Languages() []Language
- func (t *Translator) Match(code string) Language
- func (t *Translator) Save(ctx context.Context, lang Language, model Translatable) error
- func (t *Translator) Translate(ctx context.Context, model Translatable, lang Language) error
- func (t *Translator) ValidateLanguage(lang Language) error
- func (t *Translator) ValidateTranslateTags(model Translatable) error
Constants ¶
This section is empty.
Variables ¶
var ( // ErrTranslationNotFound is returned when a translation is not found ErrTranslationNotFound = errors.New("translation not found") // ErrInvalidLanguage is returned when an invalid language code is provided ErrInvalidLanguage = errors.New("invalid language") // ErrMissingTranslations is returned when required translations are missing ErrMissingTranslations = errors.New("missing required translations") // ErrInvalidTag is returned when a translate tag is malformed ErrInvalidTag = errors.New("invalid translate tag") // ErrNoPrimaryKey is returned when no primary key field is found ErrNoPrimaryKey = errors.New("no primary key field found with translate:\"primary\" tag") // ErrMultiplePrimaryKeys is returned when multiple primary key fields are found ErrMultiplePrimaryKeys = errors.New("multiple primary key fields found") // ErrInvalidModel is returned when the model doesn't implement Translatable ErrInvalidModel = errors.New("model must implement Translatable interface") )
var ( LanguageRu = Language{Code: "ru", Name: "Russian"} LanguageKk = Language{Code: "kk", Name: "Kazakh"} LanguageEn = Language{Code: "en", Name: "English"} )
Predefined languages offered as a convenience; applications are free to declare their own. The SDK does not assume any particular language set.
Functions ¶
func SetLanguageInContext ¶
SetLanguageInContext returns a child context carrying lang.
func TranslateSlice ¶
func TranslateSlice[T Translatable](ctx context.Context, t *Translator, models []T, lang Language) error
TranslateSlice translates a slice of models with type safety using generics
Types ¶
type Config ¶
type Config struct {
// Store is the translation storage backend (e.g. the postgres subpackage).
Store Store
// DefaultLanguage is the language the canonical content is authored in.
// Translating a model to the default language is a no-op.
DefaultLanguage Language
// SupportedLangs is the set of languages the application serves. It must
// include DefaultLanguage.
SupportedLangs []Language
}
Config holds configuration for the Translator.
type Data ¶
type Data struct {
ModelName string
KeyID string
Language Language
Columns map[string]string // column_name -> translation_value
CreatedAt time.Time
UpdatedAt time.Time
}
Data is a unit of translation to persist for one model instance in one language: a set of column -> value pairs.
type HeaderSource ¶ added in v0.7.0
HeaderSource names a request header to read the language from, and how to interpret its value. When AcceptLanguage is true the value is parsed as an RFC 7231 Accept-Language list (quality weights, matched on the base subtag so "ru-RU" resolves to supported "ru"); otherwise the raw value is treated as a single language code and matched exactly. Sources are tried in order — the first that yields a supported language wins.
func DefaultHeaderSources ¶ added in v0.7.0
func DefaultHeaderSources() []HeaderSource
DefaultHeaderSources are the canonical language headers, in priority order: an explicit X-Language override (exact code) then Accept-Language (parsed). To support a custom header, prepend it and keep these as the fallback:
sources := append([]translation.HeaderSource{{Name: "X-Tenant-Lang"}}, translation.DefaultHeaderSources()...)
type Language ¶
type Language struct {
Code string `json:"code"` // BCP-47 / ISO 639-1 code, e.g. "ru", "kk", "en"
Name string `json:"name"` // human-readable name, e.g. "Russian"
}
Language identifies a language by its code (and an optional display name).
func LanguageFromContext ¶
LanguageFromContext returns the language stored by the middleware. The zero Language is returned when none is set; callers that need a concrete default should compare against Translator.DefaultLanguage.
type MockStore ¶
type MockStore struct {
// contains filtered or unexported fields
}
MockStore is a mock implementation of Store for testing
func (*MockStore) CheckTranslationsExist ¶
func (m *MockStore) CheckTranslationsExist(ctx context.Context, modelName, keyID string, columns []string, langs []Language) error
CheckTranslationsExist checks if translations exist for all columns and languages
func (*MockStore) DeleteTranslations ¶
func (m *MockStore) DeleteTranslations(ctx context.Context, modelName, keyID string, lang Language) error
DeleteTranslations deletes translations from memory
func (*MockStore) GetTranslations ¶
func (m *MockStore) GetTranslations(ctx context.Context, modelName, keyID string, lang Language) (map[string]string, error)
GetTranslations retrieves translations from memory
type Store ¶
type Store interface {
// SaveTranslation saves translation data (should be executed in a transaction)
SaveTranslation(ctx context.Context, data Data) error
// GetTranslations retrieves all translations for a specific model instance and language
// Returns a map of column_name -> translation_value
GetTranslations(ctx context.Context, modelName, keyID string, lang Language) (map[string]string, error)
// DeleteTranslations deletes all translations for a specific model instance and language
DeleteTranslations(ctx context.Context, modelName, keyID string, lang Language) error
// CheckTranslationsExist checks if translations exist for all specified columns and languages
// Returns nil if all translations exist, ErrMissingTranslations otherwise
CheckTranslationsExist(ctx context.Context, modelName, keyID string, columns []string, langs []Language) error
// GetTranslationsBatch retrieves translations for multiple model instances
// Returns a map of keyID -> (column_name -> translation_value)
GetTranslationsBatch(ctx context.Context, modelName string, keyIDs []string, lang Language) (map[string]map[string]string, error)
}
Store is the interface for translation storage
type Translatable ¶
type Translatable interface {
GetTranslationKey() (modelName, keyID string)
}
Translatable is implemented by models that carry translatable fields. The returned modelName groups instances of the same type; keyID identifies the instance.
type TranslatableList ¶
type TranslatableList interface {
Translatables() []Translatable
}
TranslatableList is implemented by response payloads that wrap a collection of Translatable models, so the REST middleware can translate them in one batch without per-handler code.
type Translator ¶
type Translator struct {
// contains filtered or unexported fields
}
Translator active object holds all deps
func NewMockTranslator ¶
func NewMockTranslator() *Translator
NewMockTranslator creates a translator with mock store for testing
func (*Translator) CheckTranslationsExist ¶
func (t *Translator) CheckTranslationsExist(ctx context.Context, model Translatable) error
CheckTranslationsExist checks if translations exist for all supported languages
func (*Translator) DefaultLanguage ¶
func (t *Translator) DefaultLanguage() Language
DefaultLanguage returns the default language
func (*Translator) Delete ¶
func (t *Translator) Delete(ctx context.Context, lang Language, model Translatable) error
Delete deletes translations for a model
func (*Translator) Get ¶
func (t *Translator) Get(ctx context.Context, lang Language, model Translatable) error
Get retrieves translations for a model
func (*Translator) HTTPMiddleware ¶
func (t *Translator) HTTPMiddleware(next http.Handler) http.Handler
HTTPMiddleware is net/http middleware that resolves the request language from the canonical headers (DefaultHeaderSources) and stores it in the context for downstream handlers (and the REST translation middleware) to read via LanguageFromContext. For custom headers use LanguageMiddleware.
func (*Translator) IsDefaultLanguage ¶
func (t *Translator) IsDefaultLanguage(lang Language) bool
IsDefaultLanguage checks if the given language is the default language
func (*Translator) LanguageFrom ¶ added in v0.7.0
func (t *Translator) LanguageFrom(r *http.Request, sources ...HeaderSource) Language
LanguageFrom resolves the request language from the given header sources, in order, falling back to the translator's default. With no sources it uses DefaultHeaderSources. This is the header-agnostic core behind LanguageFromRequest and the language middleware.
func (*Translator) LanguageFromRequest ¶
func (t *Translator) LanguageFromRequest(r *http.Request) Language
LanguageFromRequest resolves the request language from the canonical language headers (DefaultHeaderSources: X-Language then Accept-Language). For custom headers use LanguageFrom, or configure the middleware via LanguageMiddleware.
func (*Translator) LanguageMiddleware ¶ added in v0.7.0
func (t *Translator) LanguageMiddleware(sources ...HeaderSource) func(http.Handler) http.Handler
LanguageMiddleware returns net/http middleware that resolves the request language from the given header sources — defaulting to DefaultHeaderSources when none are passed — and stores it in the context. This keeps the package header-agnostic: pass your own HeaderSource list (e.g. a tenant- or gateway-specific header) while keeping the canonical headers as the fallback.
func (*Translator) Languages ¶
func (t *Translator) Languages() []Language
Languages returns all supported languages by the app
func (*Translator) Match ¶
func (t *Translator) Match(code string) Language
Match maps a language code (e.g. one already resolved by an upstream i18n middleware) to a supported Language, falling back to the default when the code is empty or unsupported.
func (*Translator) Save ¶
func (t *Translator) Save(ctx context.Context, lang Language, model Translatable) error
Save saves translations for a model
func (*Translator) Translate ¶
func (t *Translator) Translate(ctx context.Context, model Translatable, lang Language) error
Translate translates a single model to the specified language Also translates nested Translatable objects in slice fields
func (*Translator) ValidateLanguage ¶
func (t *Translator) ValidateLanguage(lang Language) error
ValidateLanguage checks if the language is supported
func (*Translator) ValidateTranslateTags ¶
func (t *Translator) ValidateTranslateTags(model Translatable) error
ValidateTranslateTags validates that the model has correct translate tags
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package postgres is the Postgres implementation of translation.Store.
|
Package postgres is the Postgres implementation of translation.Store. |
|
Package translationrest wires the translation package into a skit REST router as a single rest.MidFunc that translates responses automatically.
|
Package translationrest wires the translation package into a skit REST router as a single rest.MidFunc that translates responses automatically. |