Messages are used to externalize pieces of text in order to be able to provide translations for them. Revel supports message files organized per language, automatic locale look-up, cookie-based overrides and message nesting and arguments.

Glossary

  • Locale: a combination of language and region that indicates a user language preference, eg. en-US.
  • Language: the language part of a locale, eg. en. Language identifiers are expected to be ISO 639-1 codes.
  • Region: the region part of a locale, eg. US. Region identifiers are expected to be ISO 3166-1 alpha-2 codes.

Implementation Methods

Revel allows you to implement internalization at template, or as embedded messages.

Templates when loading will automatically look for a region specific template first

app/
    views/
        Index/App.html.en
        Index/App.html.fr
        ...

You can also include templates that will check for regions automatically using the i18ntemplate. This tag acts like a traditional template tag except that it will automatically choose the region based on the ViewArgs passed in. (optionally the region may be specified as the third argument) It can be used on a page like

<p>
  Embedded Regional Template Example 
  {{i18ntemplate "templateName" .}}
</p>

Example Application

The way Revel handles message files and internationalization in general is similar to most other web frameworks out there. For those of you that wish to get started straight away without going through the specifics, there is a sample application examples/i18n which demonstrates the basics.

Configuration

File Option Description
app.conf i18n.cookie The name of the language cookie. Should always be prefixed with the Revel cookie prefix to avoid cookie name conflicts.
app.conf i18n.default_language The default locale to use in case no preferred locale could be found.

Message Files

Messages are defined in message files. These files contain the actual text that will be used while rendering the view (or elsewhere in your application if you so desire). When creating new message files, there are a couple of rules to keep in mind:

  • All message files should be stored in the messages/ directory in the application root.
  • The file extension determines the language of the message file and should be an ISO 639-1 code.
  • Message files should be UTF-8 encoded. While this is not a hard requirement, it is best practice.
  • Each message file is effectively a goconfig file and supports all goconfig features.

Organizing Message Files

There are no restrictions on message file names; a message file name can be anything as long as it has a valid extention. There is also no restriction on the amount of files per language. When the application starts, Revel will parse all message files with a valid extension in the messages/ directory and merge them according to their language. This means that you are free to organize the message files however you want.

For example, you may want to take a traditional approach and define one single message file per language:

messages/
    messages.en
    messages.fr
    ...

Another approach would be to create multiple files for the same language and organize them based on the kind of messages they contain:

messages/
    labels.en
    warnings.en
    labels.fr
    warnings.fr
    ...
Important note: while it's technically possible to define the same message key in multiple files with the same language, this will result in unpredictable behaviour. When using multiple files per language, take care to keep your message keys unique so that keys will not be overwritten after the files are merged!

Message keys and values

A message file is for all intents and purposes a goconfig file. This means that messages should be defined according to the tried and tested key-value format:

key=value

For example:

greeting=Hello 
greeting.name=Rob
greeting.suffix=, welcome to Revel!

Sections

A goconfig file is separated into sections. The default section always exists and contains any messages that are not defined in a specific section. For example:

key=value

[SECTION]
key2=value2

The key=value message is implicitly put in the default section as it was not defined under another specific section.

For message files all messages should be defined in the default section unless they are specific to a certain region (see Regions for more information).

Note: sections are a goconfig feature.

Regions

Region-specific messages should be defined in sections with the same name. For example, suppose that we want to greet all English speaking users with "Hello", all British users with "Hey" and all American users with "Howdy". In order to accomplish this, we could define the following message file greeting.en:

greeting=Hello

[GB]
greeting=Hey

[US]
greeting=Howdy

For users who have defined English (en) as their preferred language, Revel would resolve greeting to Hello. Only in specific cases where the user’s locale has been explicitly defined as en-GB or en-US would the greeting message be resolved using the specific sections.

Important: messages defined under a [section] that is not a valid region are technically allowed but ultimately useless as they will never be resolved.

Referencing and arguments

Referencing

Messages in message files can reference other messages. This allows users to compose a single message from one or more other messages. The syntax for referencing other messages is %(key)s. For example:

greeting=Hello 
greeting.name=Rob
greeting.suffix=, welcome to Revel!
greeting.full=%(greeting)s %(greeting.name)s%(greeting.suffix)s

Notes:

  • Referencing is a goconfig feature.
  • Because message files are merged, it's perfectly possible to reference messages in other files provided they are defined for the same language.

Arguments

Messages support one or more arguments. Arguments in messages are resolved using the same rules as the go fmt package. For example:

greeting.name_arg=Hello %s!

Arguments are resolved in the same order as they are given, see Resolving messages.

Resolving the client locale

In order to figure out which locale the user prefers Revel will look for a usable locale in the following places:

  1. Language cookie

    • For every request, Revel will look for a cookie with the name defined in the application configuration i18n.cookie.
    • If such a cookie is found, its value is assumed to be the current locale.
    • All other resolution methods will be skipped when a cookie has been found.
  2. Accept-Language HTTP header

    • Revel will automatically parse the Accept-Language HTTP header for each incoming request.
    • Each of the locales in the Accept-Language header value is evaluated and stored in order of qualification according to the HTTP specification - in the current Request instance.
    • This information is used later by the various message resolving functions to determine the current locale.
    • For more information see Parsed Accept-Language HTTP header.
  3. Default language

Note: When the requested message could not be resolved at all, a specially formatted string containing the original message is returned.

Note: the Accept-Language header is always parsed and stored in the current Request, even when a language cookie has been found. In such a case, the values from the header are simply never used by the message resolution functions, but they're still available to the application in case it needs them.

Retrieving the current locale

The application code can access the current locale from within a Request using the Request.Locale property. For example:

func (c App) Index() revel.Result {
	currentLocale := c.Request.Locale
	c.Render(currentLocale)
}

From a template, the current locale can be retrieved from the currentLocale property of viewArgs. For example:

    <p>My Locale is: {{.currentLocale}}</p>

Parsed Accept-Language HTTP header

In case the application needs access to the Accept-Language HTTP header for the current request it can retrieve it from the Request instance of the Controller. The AcceptLanguages field

  • which is a slice of AcceptLanguage instances - contains all parsed values from the respective header, sorted per qualification with the most qualified values first in the slice. For example:
func (c App) Index() revel.Result {
    // Get the string representation of all parsed accept languages
    c.ViewArgs["acceptLanguageHeaderParsed"] = c.Request.AcceptLanguages.String()
    // Returns the most qualified AcceptLanguage instance
    c.ViewArgs["acceptLanguageHeaderMostQualified"] = c.Request.AcceptLanguages[0]

    c.Render()
}

For more information see the HTTP specification.

Resolving messages

Messages can be resolved from either a controller or a view template.

Controller

Each controller has a Message(message string, args ...interface{}) function that can be used to resolve messages using the current locale. For example:

func (c App) Index() revel.Result {
	c.ViewArgs["controllerGreeting"] = c.Message("greeting")
	c.Render()
}

Template

To resolve messages using the current locale from templates there is a template function msg that you can use. For example:

<p>Greetings without arguments: {{msg . "greeting"}}</p>
<p>Greetings: {{msg . "greeting.full.name" "Tommy Lee Jones"}}</p>

Notes:

  • The signature of the msg function is msg . "message name" "argument" "argument". If there are no arguments, simply do not include any.
  • The I18nFilter filter must be enabled (default) or the currentLocale RenderArg set for message substitution to work.