29. Localization

Locale

The Locale class is the hub API for Fantom's localization infrastructure. Locales are used to represent a language via an ISO 639 two letter code. A locale may also optionally represent a specific country or region via a ISO 3166 two letter code. Common locales include:

Language Only:
  en     English
  es     Spanish
  fr     French
  de     German
  it     Italian
  ja     Japanese
  ko     Korean
  zh     Chinese
Language and Country:
  en-US  United States
  en-GB  United Kingdom
  es-ES  Spain
  es-MX  Mexico
  fr-FR  France
  de-DE  Germany
  en-CA  Canada (English)
  fr-CA  Canada (French)
  zh-CN  China (Simplified)
  zh-TW  Taiwan

When the Fantom VM is started it will create a default locale based on the underlying platform. For example on a Java VM, the default Fantom locale will be initialized from java.util.Locale (which in turn is initialized from the operating system's default locale).

Locales are configured as an actor local variable. Use Locale.cur and Locale.setCur to get and set the current thread's locale.

By convention Locale is never passed as a parameter to Fantom APIs. Rather the locale is implied by the current actor. As a general rule APIs which are locale sensitive will include the term locale in their method name.

Use Locale.use to execute a body of code using a different locale:

echo(Locale.cur)
Locale("zh-CN").use {  echo(Locale.cur) }
echo(Locale.cur)

Localized Properties

All the strings displayed to users should typically be pulled out into localized props files to enable easy language translation. Each pod may define one localized property map with as many keys are desired. Localized properties are defined in props files as pod resource files under the locale directory. An example set of localized props files:

locale/en.props
locale/en-US.props
locale/en-CA.props
locale/fr.props
locale/fr-CA.props

The Env.locale API is used to lookup a localized property. Typically you will use the Pod.locale convenience method:

Button(pod.locale("ok"))
Button(pod.locale("cancel"))

Looking up localization is delegated to the current Env. But the standard resolution rules are:

  1. Env.props(pod, `locale/{locale}.props`)
  2. Env.props(pod, `locale/{lang}.props`)
  3. Env.props(pod, `locale/en.props`)
  4. Fallback to pod::key unless def specified

Env.props first looks in the pod's etc directory for overrides, then checks if the pod contains a resource file.

Best practice is to ensure that all properties are mapped in en.props file as your fallback defaults. Then store localized translations in language files such as fr.props, de.props, etc. Typically you will only use country specific files such as en-US or en-GB for regional terms.

Locale Literals

String interpolation supports a special mode used to make working with localized props easy. The following interpolation formats are supported:

  • $<key> unqualified key
  • $<pod::key> qualified key
  • $<key=def> unqualified key with default value

The simplest locale literal is an unqualified key which is just a shortcut for Pod.locale using the enclosing type:

// original code
class Foo { Void main() { echo("$<bar.baz>") } }

// translated into
class Foo { Void main() { echo(#Foo.pod.locale("bar.baz")) } }

You can also use a qualified key to lookup a localized prop in an external pod:

// original code
"$<fwt::cancel.name>"

// translated into
Pod.find("fwt").locale("cancel.name")

The last format lets you actually define the key's value right in your Fantom source code:

// orginal code
"$<fileNotFound=File not found>: $file"

// translates into
EnclosingType#.pod.locale("fileNotFound", "File not found") + ": $file"

// and automatically adds the key/value pair to locale/en.props
fileNotFound=File not found

If your pod doesn't have an explicit "locale/en.props" resource then it is automatically created. If it does exist then interpolated key/values are automatically merged into the existing props file. It is a compile time error to declare a key's value in multiple places; each key must be defined exactly once in either en.props or in an interpolated string.