go-l10n is a command-line utiltity that generates Golang files from localization files defined in YAML, JSON or TOML format.
It supports strings with arguments that come from Golang code and variables defined within messages, and allows to create different messages depending on the value of the arguments.
Install it using go
command:
go install github.com/infastin/go-l10n@latest
Simplest message possible looks like this:
Welcome: "Welcome, traveler!"
The message identifier can consist of any characters you want.
Here it is Welcome
.
If you want to display arbitrary string instead of traveler
,
you can make use of arguments, that will be passed from Golang:
Welcome: "Welcome, ${name}!"
Arguments are contained inside of ${...}
blocks.
Argument names in generated code will be the same as in YAML code.
So if you have argument ${very_beautiful_name}
, in Go it also will be very_beautiful_name
.
Argument names can only contain Latin letters (a-zA-Z).
In order to escape $
just write it twice.
All arguments are strings by default. But you can change it by prefixing argument name with one of the following formats:
s:
—string
f:
—float64
d:
—int
v:
-any
S:
—fmt.Stringer
You can also format arguments using format specification similar to Golang's fmt
package.
Format consists of:
- Flags
- Width
- Precision
- Type specifier (ones specified above)
- Golang format specifier for the type (for example, it can be
x
for integers to output them in hex format)
Flags, width, precision and format specifiers are all the same as in Golang's fmt
package.
Format goes before argument name and is separated from it with :
:
BankAccount: "You have $$${+.3f:money} dollars in your bank account."
If you want your message to look different depending on some integral argument, you can use plural
block:
YouAreLate:
plural:
arg: "count"
one: "You are 1 minute late."
other: "You are ${count} minutes late."
plural
block consists of 5 fields:
arg
— name of the argument depending on the value of which different messages will be returnedzero
- message whenarg
equals zeroone
- message whenarg
equals onemany
- message whenarg
is more than oneother
- message to be returned when nothing above is true or not specified
arg
is required, and the argument specified in this field is forced to be int
.
You can rewrite example above using variables. Variables are defined within a message and only visible within it:
YouAreLate:
variables:
minutes:
plural:
arg: "count"
one: "minute"
other: "minutes"
string: "You are ${count} &{minutes} late."
Variables are contained within &{...}
blocks.
Variables don't support formatting.
Variable names can only contain Latin letters and underscores (a-zA-Z_).
In order to escape &
just write it twice.
You can use arguments inside of variable values:
YouAreLate:
variables:
minutes:
plural:
arg: "count"
zero: "0 minutes"
one: "1 minute"
other: "${count} minutes"
string: "You are &{minutes} late."
Also variables can be simple strings (even though it's not very useful):
HelloWorld:
variables:
world: "World"
string: "Hello, &{world}!"
Everything shown above can also be done in JSON or TOML.
Now you write a bunch of messages in files withing one directory whose names match this regexp pattern:
([a-z_]+)\.([a-z_]+)\.(yaml|yml|json|toml)
Or, to put it more simply: {{.Name}}.{{.Lang}}.{{.Ext}}
.
Also you can change the regexp pattern with -p, --pattern=PATTERN
flag to go-l10n
command.
But it must contain three groups in the following order:
- Name — will be used when generating files, but doesn't really matter
- Language —
en
,de
,es
, etc - Extension —
yaml
,yml
,json
ortoml
Now you run a command:
go-l10n -d YOUR_DIRECTORY -o OUTPUT_DIRECTORY
If your messages are correct, it will generate a bunch of Go files in the output directory
with the package name being l10n
. You can change it with -P, --package=NAME
flag.
The file that you wanna look into is l10n.go
:
// Code generated by go-l10n; DO NOT EDIT.
package l10n
type Localizer interface {
BankAccount(money float64) string
YouAreLate() string
}
var mapLangToLocalizer = map[string]Localizer{
"en": en_Localizer{},
"ru": ru_Localizer{},
}
var Supported = []string{
"en",
"ru",
}
func New(lang string) (loc Localizer, ok bool) {
loc, ok = mapLangToLocalizer[lang]
return loc, ok
}
func Language(loc Localizer) string {
switch loc.(type) {
case en_Localizer:
return "en"
case ru_Localizer:
return "ru"
default:
return ""
}
}
Slice Supported
contains all supported languages.
With New
function you can get yourself Localizer
for a given language.
And with Language
function you can get the language from Localizer
.
Once you obtain Localizer
, you can simply call its methods,
which are named exactly like messages defined in your localization files,
with the arguments that you've specified, that are named exactly as you defined them,
to get yourself a localized message.