Localization
Introduction
The Goyave framework provides a convenient way to support multiple languages within your application. Out of the box, Goyave only provides the en-US
language, but you can add as many as you want.
.
└── resources
└── lang
└── en-US (language name)
├── fields.json (optional)
├── locale.json (optional)
└── rules.json (optional)
Each language has its own directory and should be named with an ISO 639-1 language code. You can also append a variant to your languages: en-US
, en-UK
, fr-FR
, fr-CA
, ... Case is important.
Each language directory contains three files. Each file is optional.
fields.json
: field names translations and field-specific rule messages.locale.json
: all other language lines.rules.json
: validation rules messages.
INFO
All directories in the resources/lang
directory are automatically loaded when the server is created.
Language files
Fields
The fields.json
file contains the field names translations. Translating field names helps making more expressive messages instead of showing the technical field name to the user.
Example:
{
"email": "email address"
}
As a result, the :field
placeholder in validation error messages will be filled with "email address" instead of the raw field name "email".
INFO
Learn more about validation messages placeholders in the validation section.
Locale
The locale.json
file contains all language lines that are not related to validation. This is the place where you should write the language lines for your user interface or for the messages returned by your controllers.
Example:
{
"product.created": "The product has been created with success.",
"product.deleted": "The product has been deleted with success."
}
TIP
It is a good practice to use dot-separated names for language lines to help making them clearer and more expressive.
Rules
The rules.json
file contains the validation rules messages. These messages can have placeholders, which will be automatically replaced by the validator with dynamic values. If you write custom validation rules, their messages shall be written in this file.
Example:
{
"integer": "The :field must be an integer.",
"starts_with": "The :field must start with one of the following values: :values.",
"same": "The :field and the :other must match."
}
TIP
- If you define the
en-US
language in your application, the default language lines will be overridden by the ones in your language files, and all the undefined ones will be kept. - The validation rules localization is explained in more details here.
Manual loading
It is possible to load a language directory manually from another location. If the loaded language is already available in your application, the newly loaded one will override the previous by being merged into it.
import (
"embed"
"fmt"
"os"
"goyave.dev/goyave/v5"
"goyave.dev/goyave/v5/lang"
"goyave.dev/goyave/v5/util/errors"
"goyave.dev/goyave/v5/util/fsutil"
)
//go:embed resources
var resources embed.FS
func main() {
server, err := goyave.New(goyave.Options{})
if err != nil {
fmt.Fprintln(os.Stderr, err.(*errors.Error).String())
os.Exit(1)
}
resources := fsutil.NewEmbed(resources)
langFS, err := resources.Sub("resources/lang")
if err != nil {
server.Logger.Error(err)
os.Exit(1)
}
err = server.Lang.Load(langFS, "en-CA", "lang/en-CA")
err = server.Lang.LoadDirectory(langFS, "lang")
//...
}
TIP
Any filesystem implementing fsutil.FS
can be used. Use osfs.FS
if you don't want to use an embedded filesystem.
Using localization
There are two important structures to know about:
*lang.Languages
: a container for all loaded languages, it defines the default language as well. This container is accessible to all components.*lang.Language
: a single language, containing all localized lines.
When an incoming request enters your application, the built-in language middleware checks if the Accept-Language
header is set, and set the *goyave.Request.Lang
field with the identified *lang.Language
, or the default one.
You can get a localized string using the language's Get()
mehtod.
func (ctrl *Controller) Handle(response *goyave.Response, request *goyave.Request) {
request.Lang.Get("customMessage")
// or
ctrl.Lang().GetDefault().Get("customMessage")
ctrl.Lang().Get("en-US", "customMessage")
}
Paths
- Custom lines defined in
locale.json
are accessed with their key directly. - Validation rules messages defined in
rules.json
are accessed with thevalidation.rules
prefix:validation.rules.<rule_name>
validation.rules.<rule_name>.element
validation.rules.<rule_name>.string
validation.rules.<rule_name>.numeric
validation.rules.<rule_name>.array
validation.rules.<rule_name>.file
validation.rules.<rule_name>.object
- Field names defined in
fields.json
are accessed with thevalidation.fields
prefix:validation.fields.<field_name>
INFO
Entries that do not exist are returned as-is. For example, if you Get()
a lang entry validation.rules.nonExisting
, the string returned will be validation.rules.nonExisting
.
Placeholders
Language lines can contain placeholders. Placeholders are identified by a colon directly followed by the placeholder name:
{
"greetings": "Greetings, :username!"
}
The last parameter of the `Get()` method is a **variadic associative slice** of placeholders and their replacement. In the following example, the placeholder `:username` will be replaced with the `Name` field in the user struct.
```go
language.Get("greetings", ":username", user.Name) // "Greetings, Taylor!"
You can provide as many as you want:
language.Get("greetings-with-date", ":username", user.Name, ":day", "Monday") // "Greetings, Taylor! Today is Monday"
TIP
When a placeholder is given, all occurrences are replaced.
{
"popular": ":product are very popular. :product sales exceeded 1000 last week."
}
language.Get("popular", ":product", "Lawnmowers")
// "Lawnmowers are very popular. Lawnmowers sales exceeded 1000 last week."